-
Notifications
You must be signed in to change notification settings - Fork 0
feat(user): 슈퍼계정 생성 #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "feat/#47_\uAD00\uB9AC\uC790\uD398\uC774\uC9C0\uC8FC\uBB38\uC870\uD68C"
Conversation
- SUPER_ADMIN 추가 - git 추적에 빠졌던 파일 git 추적하도록 추가
|
""" Walkthrough이 변경사항은 다양한 도메인(북마크, 메뉴, 매장, 주문, 예약, 토큰, 인증 등)에 대한 컨트롤러, 서비스, DTO, 예외 처리 및 설정 클래스를 신규로 추가합니다. JWT 기반 인증, OAuth2 소셜 로그인, S3 파일 업로드, 글로벌 예외 처리, CORS 및 비동기 설정 등 백엔드 시스템의 핵심 기능이 모듈별로 구현되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant AuthController
participant JwtUtil
participant TokenService
participant UserRepository
participant TokenRepository
Client->>AuthController: POST /api/refresh-token (refreshToken)
AuthController->>JwtUtil: getUserId, getRole from refreshToken
AuthController->>TokenService: validateToken(refreshToken, userId)
TokenService->>TokenRepository: findByUserId(userId)
TokenRepository-->>TokenService: Token or not found
TokenService-->>AuthController: 유효 여부 반환
alt 토큰 유효
AuthController->>JwtUtil: createAccessToken(...)
AuthController->>JwtUtil: createRefreshToken(...)
AuthController->>TokenService: updateRefreshToken(userId, old, new)
TokenService->>TokenRepository: findByUserId(userId)
TokenService->>TokenRepository: delete old, save new
TokenService-->>AuthController: 완료
AuthController-->>Client: 200 OK (accessToken, refreshToken)
else 토큰 만료/불일치
AuthController-->>Client: 401 Unauthorized
end
sequenceDiagram
participant Client
participant OAuth2Login
participant CustomOAuth2UserService
participant UserRepository
participant TokenRepository
participant JwtUtil
Client->>OAuth2Login: OAuth2 인증 요청
OAuth2Login->>CustomOAuth2UserService: loadUser
CustomOAuth2UserService->>UserRepository: findByEmail
alt 신규 사용자
CustomOAuth2UserService->>UserRepository: save(User)
end
CustomOAuth2UserService-->>OAuth2Login: CustomOAuth2User
OAuth2Login->>JwtUtil: createAccessToken, createRefreshToken
OAuth2Login->>TokenRepository: save(Token)
OAuth2Login-->>Client: accessToken, refreshToken 반환
Possibly related PRs
Suggested labels
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 49
🔭 Outside diff range comments (1)
application-admin/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java (1)
1-1: 패키지명 오타 수정이 필요합니다패키지명에 오타가 있습니다:
serivce→service-package com.nowait.applicationadmin.user.serivce; +package com.nowait.applicationadmin.user.service;
🧹 Nitpick comments (37)
.github/ISSUE_TEMPLATE/백엔드-이슈.md (2)
2-7:assignees필드 값이 비어 있음
빈 문자열('')은 YAML에서 시맨틱하게 애매합니다. 명시적으로 빈 배열로 두거나 필드를 제거해 혼란을 줄여주세요.-assignees: '' +# 특정 담당자가 없다면 필드를 제거하거나 +# assignees: []
10-15: 이슈 본문 템플릿이 부족합니다
현재 내용·재현 절차·기대 결과 등 기본 정보를 수집할 수 있는 입력란이 없습니다. 템플릿을 구체화해 이슈 품질을 높이는 것을 권장드립니다.# 이슈 내용 - - +# 문제 설명 +<!-- 해당 버그/기능 요청에 대해 구체적으로 설명해주세요 --> + +# 재현 절차 +1. +2. + +# 기대 결과 +<!-- 정상 동작 또는 개선 방향을 명확히 서술해주세요 --> + +# 스크린샷/로그 +<!-- 가능하다면 첨부하여 주세요 --> # 작업 목록 - [ ]application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java (1)
13-19: 스레드 풀 설정 개선 제안더 안정적인 스레드 풀 운영을 위해 다음 설정들을 추가하는 것을 권장합니다:
public Executor s3UploadExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix("S3Upload-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.setKeepAliveSeconds(60); + executor.setAllowCoreThreadTimeOut(true); executor.initialize(); return executor; }infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java (1)
27-27: 타입 캐스팅 제거 권장불필요한 타입 캐스팅을 제거하고
AmazonS3인터페이스를 사용하는 것을 권장합니다:-return (AmazonS3Client)AmazonS3ClientBuilder.standard() +return AmazonS3ClientBuilder.standard()그리고 반환 타입을
AmazonS3로 변경:-public AmazonS3Client amazonS3Client() { +public AmazonS3 amazonS3Client() {infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java (2)
31-31: TODO 주석 해결 필요
MultipartFile의존성 제거에 대한 TODO 주석이 있습니다. 이는 인프라 레이어가 Spring에 의존하는 것을 방지하기 위한 중요한 아키텍처 결정입니다.MultipartFile을 대체할 수 있는 추상화된 인터페이스나 DTO를 설계하는 것을 도와드릴까요?
46-52: 동기식 삭제 메서드 일관성 검토업로드는 비동기로 처리하지만 삭제는 동기식으로 처리하는 것이 일관되지 않습니다. 사용 사례에 따라 삭제도 비동기로 처리하는 것을 고려해보세요.
+@Async("s3UploadExecutor") +public CompletableFuture<Void> deleteAsync(String filename) { + try { + amazonS3Client.deleteObject(bucket, filename); + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + throw new RuntimeException("S3 파일 삭제 실패", e); + } +}api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java (1)
5-9: 추가 생성자 제공을 고려해보세요.현재 예외 클래스는 기본 생성자만 제공하고 있습니다. 더 유연한 예외 처리를 위해 커스텀 메시지를 받는 생성자를 추가하는 것을 고려해보세요.
public class ReservationNotFoundException extends RuntimeException { public ReservationNotFoundException() { super(ErrorMessage.NOTFOUND_RESERVATION.getMessage()); } + + public ReservationNotFoundException(String message) { + super(message); + } + + public ReservationNotFoundException(String message, Throwable cause) { + super(message, cause); + } }application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java (1)
25-25: 주석 개선이 필요합니다.주석의 설명을 더 명확하게 개선해보세요.
- source.registerCorsConfiguration("/**", config); //** 뜻은 모든 URL 경로에 적용한다는 의미 + source.registerCorsConfiguration("/**", config); // /** 는 모든 URL 경로에 적용한다는 의미security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java (1)
3-9: 추가 생성자 제공을 고려해보세요.현재 예외 클래스는 기본 생성자만 제공합니다. 더 유연한 예외 처리를 위해 추가 생성자를 고려해보세요.
public class RefreshTokenNotFoundException extends ResourceNotFoundException { public RefreshTokenNotFoundException() { super(ErrorMessage.REFRESH_TOKEN_NOT_FOUND); } + + public RefreshTokenNotFoundException(String message) { + super(message); + } + + public RefreshTokenNotFoundException(String message, Throwable cause) { + super(message, cause); + } }api-user/src/main/java/com/example/apiuser/store/service/StoreService.java (1)
10-14: 반환 타입 일관성 개선 고려
searchStoresByName메서드만List<StoreReadDto>를 반환하고 있어 다른 메서드들과 일관성이 없습니다.getAllStores()는StoreReadResponse를 반환하는데, 검색 결과도 동일한 래핑 타입을 사용하는 것을 고려해보세요.- List<StoreReadDto> searchStoresByName(String name); + StoreReadResponse searchStoresByName(String name);api-user/src/main/java/com/example/apiuser/ApiUserApplication.java (1)
10-32: 패키지 구조 일관성 개선 고려현재 엔티티와 리포지토리 스캔에서 패키지 명명 규칙이 일관되지 않습니다 (
com.example.*,com.nowait.*,com.nowaiting.*). 향후 유지보수를 위해 패키지 구조를 통일하는 것을 고려해보세요.패키지 명명 규칙을 통일하여 다음과 같이 개선할 수 있습니다:
- 모든 도메인을
com.nowait.domain.*하위로 통일- 또는 현재 구조를 유지하되 명명 규칙을 문서화
api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java (1)
15-15: 주석과 필드명 불일치 수정 필요주석에서 "주문 상태"라고 되어 있지만 실제 필드는
orderId입니다. 주석을 필드의 실제 목적에 맞게 수정해주세요.- private Long orderId; // 주문 ID // 주문 상태 (예: "주문완료", "배송중" 등) + private Long orderId; // 주문 IDapi-user/src/main/java/com/example/apiuser/menu/service/MenuService.java (1)
30-38: 잠재적 N+1 쿼리 문제 검토메뉴 목록을 조회한 후 각 메뉴마다 이미지를 개별적으로 조회하고 있어 N+1 쿼리 문제가 발생할 수 있습니다.
MenuRepository에서 메뉴와 이미지를 한 번에 조회하는 메서드를 고려해보세요.// 예시: JOIN FETCH를 사용한 최적화 List<Menu> menus = menuRepository.findAllByStoreIdWithImages(storeId);domain-token/src/main/java/com/example/domaintoken/entity/Token.java (2)
36-36: @column 어노테이션 누락
expiredDate필드에@Column어노테이션이 누락되어 있습니다. 일관성을 위해 다른 필드들과 마찬가지로 어노테이션을 추가하는 것이 좋습니다.+@Column private LocalDateTime expiredDate;
25-54: 토큰 관리 기능 추가 제안현재 엔티티는 기본적인 구조만 가지고 있습니다. 토큰의 업데이트와 만료 확인을 위한 메소드를 추가하면 더 유용할 것 같습니다.
+public void updateRefreshToken(String newRefreshToken, LocalDateTime newExpiredDate) { + this.refreshToken = newRefreshToken; + this.expiredDate = newExpiredDate; +} + +public boolean isExpired() { + return LocalDateTime.now().isAfter(this.expiredDate); +}security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java (1)
56-56: 기본 역할을 설정 가능하도록 만드세요.사용자의 기본 역할이 하드코딩되어 있습니다. 이를 설정 가능하도록 변경하는 것을 고려해보세요.
+ @Value("${oauth2.default-role:USER}") + private String defaultRole; + - .role(Role.USER) // 일반 유저 설정 + .role(Role.valueOf(defaultRole)) // 설정 가능한 기본 역할api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java (1)
38-44: 예약 생성 시 중복 체크를 추가하세요.동일한 사용자가 같은 매장에 중복 예약을 생성할 수 있습니다. 중복 예약 방지 로직을 추가하는 것을 고려해보세요.
+ // 중복 예약 체크 (선택사항) + boolean hasActiveReservation = reservationRepository.existsByUserAndStoreAndStatusIn( + user, store, Arrays.asList(ReservationStatus.WAITING, ReservationStatus.CONFIRMED)); + if (hasActiveReservation) { + throw new IllegalArgumentException("이미 해당 매장에 활성 예약이 존재합니다."); + } + Reservation reservation = Reservation.builder() .store(store) .user(user)api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java (1)
38-46: 예외 처리를 추가하세요.서비스 레이어에서 발생할 수 있는 예외에 대한 처리가 없습니다. try-catch 블록을 추가하거나 GlobalExceptionHandler에서 처리하도록 하세요.
현재 GlobalExceptionHandler가 있다면 해당 핸들러에서 예외를 처리하도록 하는 것이 좋습니다. 그렇지 않다면 다음과 같이 예외 처리를 추가하세요:
public ResponseEntity<?> create( @PathVariable Long storeId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User, @RequestBody ReservationCreateRequestDto requestDto) { + try { ReservationCreateResponseDto response = reservationService.create(storeId, customOAuth2User, requestDto); return ResponseEntity .status(HttpStatus.CREATED) .body( ApiUtils.success( response ) ); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body(ApiUtils.error(e.getMessage(), HttpStatus.BAD_REQUEST)); + } catch (Exception e) { + return ResponseEntity.internalServerError().body(ApiUtils.error("예약 생성 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR)); + } }security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java (1)
30-41: 권한 처리 방식을 간소화하세요.익명 내부 클래스를 사용하지 않고 더 간단한 방식으로 권한을 처리할 수 있습니다.
@Override public Collection<? extends GrantedAuthority> getAuthorities() { - Collection<GrantedAuthority> authorities = new ArrayList<>(); - - authorities.add(new GrantedAuthority() { - @Override - public String getAuthority() { - return user.getRole().getName(); // 유저의 권한 리턴 - } - }); - - return authorities; + return Collections.singletonList(() -> user.getRole().getName()); }api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java (2)
15-18: API 기본 경로에 슬래시 누락
@RequestMapping("/v1/menus")형태로 선행/를 붙여 REST 경로를 일관성 있게 유지해 주세요.
22-31: 응답 타입과 상태 코드 간소화 제안
ResponseEntity.ok(ApiUtils.success(...))로 충분하며, 제네릭 타입을 명시하면 클라이언트 코드 가독성이 좋아집니다.- public ResponseEntity<?> getMenusByStoreId(@PathVariable Long storeId) { - return ResponseEntity - .status(HttpStatus.OK) - .body(ApiUtils.success(menuService.getMenusByStoreId(storeId))); + public ResponseEntity<MenuReadResponse> getMenusByStoreId(@PathVariable Long storeId) { + return ResponseEntity.ok( + ApiUtils.success(menuService.getMenusByStoreId(storeId)) + ); }security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java (2)
30-32: 필드 네이밍 컨벤션 불일치
스프링 DI 변수를 대문자로 시작하면 가독성이 떨어집니다.oAuth2LoginSuccessHandler로 소문자 시작을 권장합니다.
15-16: 사용되지 않는 import 정리
CorsConfigurationSource가 사용되지 않습니다. 제거하여 경고를 줄이세요.api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java (3)
16-18: 기본 경로에 슬래시 추가 필요
@RequestMapping("/v1/stores")가 더 일반적입니다.
24-33: 응답 제네릭 명시 및 상태 코드 간소화
ResponseEntity.ok(...)로 충분하며 반환 DTO를 제네릭으로 지정해 주세요.
46-55: 검색 파라미터 검증 추가 제안
name이 빈 문자열일 때 예외 처리(400) 또는 전체 목록 반환 규칙을 명확히 해 주세요.api-user/src/main/java/com/example/apiuser/token/service/TokenService.java (1)
61-64: 토큰 갱신 로직의 삭제-삽입 대신 갱신 사용
동시성 이슈를 줄이기 위해 delete-insert 대신 엔티티의 refreshToken 필드만 업데이트하는 편이 안전합니다.security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java (1)
52-66: 에러 응답 헤더 부족
401 응답 시Content-Type: text/plain;charset=UTF-8등을 지정하지 않아 클라이언트가 메시지를 파싱하지 못할 수 있습니다.api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java (2)
32-42: 매개변수 사이의 공백이 누락되었습니다.
@PathVariable Long storeId,@AuthenticationPrincipal CustomOAuth2User customOAuth2User부분에서 콤마 뒤에 공백이 없어 가독성이 떨어집니다.-public ResponseEntity<?> createBookmark(@PathVariable Long storeId,@AuthenticationPrincipal CustomOAuth2User customOAuth2User) { +public ResponseEntity<?> createBookmark(@PathVariable Long storeId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) {
58-66: 매개변수 사이의 공백이 누락되었습니다.삭제 엔드포인트에서도 동일한 공백 문제가 있습니다.
-public ResponseEntity<?> deleteBookmark(@PathVariable Long bookmarkId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { +public ResponseEntity<?> deleteBookmark(@PathVariable Long bookmarkId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) {api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java (1)
64-65: ResponseEntity 체이닝에서 메서드 호출 형식이 일관되지 않습니다.64번째 줄의
ResponseEntity.뒤에 바로 줄바꿈이 있어 다른 부분과 형식이 일치하지 않습니다.- return ResponseEntity. - status(HttpStatus.OK) + return ResponseEntity + .status(HttpStatus.OK) .body( ApiUtils.success(orderItems) );api-user/src/main/java/com/example/apiuser/order/service/OrderService.java (3)
111-118: MD5 해시는 충돌 가능성이 높습니다단순 중복 검출 용도라도 MD5 대신 SHA-256 등 최신 해시를 사용하는 것이 안전하며 구현 난이도 차이도 없습니다.
예)DigestUtils.sha256DigestAsHex(raw.getBytes())
120-127: 시간 비교 시Instant사용 권장
LocalDateTime은 시스템 timezone 을 암묵적으로 사용해 애플리케이션과 DB timezone 이 다를 때 오탐 위험이 있습니다.
Instant또는ZonedDateTime으로 변경하면 명시적인 UTC 기준 비교가 가능합니다.
90-100: N+1 쿼리 가능성 확인
userOrders조회 후order.getOrderItems()를 접근하면 LAZY 로딩 시 주문 수 만큼 추가 쿼리가 발생할 수 있습니다.
findByStore_StoreIdAndTableIdAndSessionId(...)에서@EntityGraph로orderItems를 즉시 로딩하도록 수정해 주세요.security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java (1)
30-36: 메서드명 오타
handlerOAuth2AuthenticationException→handleOAuth2AuthenticationException으로 통일하면 가독성과 일관성이 좋아집니다.api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java (2)
3-4: 정적 임포트와 일반 임포트 혼용
ErrorMessage를 정적 임포트와 일반 임포트 둘 다 사용하고 있습니다. 한 가지 방식으로 통일해 주세요.
90-92: 로그 메시지 반복 오타
handleUnauthorizedExceptionException등Exception이 두 번 들어간 메서드명이 다수 있습니다. 수정 권장합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (62)
.github/ISSUE_TEMPLATE/백엔드-이슈.md(1 hunks)api-user/src/main/java/com/example/apiuser/ApiUserApplication.java(1 hunks)api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java(1 hunks)api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java(1 hunks)api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java(1 hunks)api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java(1 hunks)api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java(1 hunks)api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java(1 hunks)api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java(1 hunks)api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java(1 hunks)api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java(1 hunks)api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java(1 hunks)api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java(1 hunks)api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java(1 hunks)api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java(1 hunks)api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java(1 hunks)api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java(1 hunks)api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java(1 hunks)api-user/src/main/java/com/example/apiuser/order/service/OrderService.java(1 hunks)api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java(1 hunks)api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java(1 hunks)api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java(1 hunks)api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java(1 hunks)api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java(1 hunks)api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java(1 hunks)api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java(1 hunks)api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java(1 hunks)api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java(1 hunks)api-user/src/main/java/com/example/apiuser/store/service/StoreService.java(1 hunks)api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java(1 hunks)api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java(1 hunks)api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java(1 hunks)api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java(1 hunks)api-user/src/main/java/com/example/apiuser/token/service/TokenService.java(1 hunks)application-admin/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java(1 hunks)application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java(1 hunks)application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java(1 hunks)application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java(1 hunks)common/src/main/java/com/nowait/common/enums/Role.java(1 hunks)domain-token/src/main/java/com/example/domaintoken/entity/Token.java(1 hunks)domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java(1 hunks)domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java(1 hunks)infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java(1 hunks)infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java(1 hunks)security-front/src/main/java/com/nowait/auth/config/CorsConfig.java(1 hunks)security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java(1 hunks)security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java(1 hunks)security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java(1 hunks)security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java(1 hunks)security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java(1 hunks)security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java(1 hunks)security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java(1 hunks)security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java(1 hunks)security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java(1 hunks)security-front/src/main/java/com/nowait/exception/BusinessException.java(1 hunks)security-front/src/main/java/com/nowait/exception/ErrorMessage.java(1 hunks)security-front/src/main/java/com/nowait/exception/ErrorResponse.java(1 hunks)security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java(1 hunks)security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java(1 hunks)security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java(1 hunks)security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java(1 hunks)security-front/src/main/java/com/nowait/exception/UnauthorizedException.java(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (15)
api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java (2)
api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java (1)
Getter(8-20)api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java (1)
Getter(12-39)
api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java (2)
api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java (1)
Getter(8-22)api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java (1)
Getter(12-39)
api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java (2)
api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java (1)
Getter(11-32)api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java (1)
Getter(8-22)
domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java (1)
security-front/src/main/java/com/nowait/exception/BusinessException.java (1)
BusinessException(3-14)
security-front/src/main/java/com/nowait/exception/BusinessException.java (1)
domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java (1)
BusinessException(5-16)
api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java (2)
api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java (1)
Getter(8-20)api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java (1)
Getter(9-21)
api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java (3)
api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java (1)
Getter(11-28)api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java (1)
Getter(6-11)api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java (1)
Getter(10-29)
api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java (3)
api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java (1)
Getter(6-11)api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java (1)
Getter(9-15)api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java (1)
Getter(8-16)
api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java (1)
api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java (1)
Getter(9-24)
security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java (2)
security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java (1)
RequiredArgsConstructor(14-60)security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java (1)
RequiredArgsConstructor(25-93)
api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java (3)
api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java (1)
Service(21-78)domain-user/src/main/java/com/nowait/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java (1)
RequiredArgsConstructor(14-60)
api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java (1)
api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java (1)
Getter(6-10)
security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java (2)
security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java (1)
Component(14-30)security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java (1)
Component(31-80)
api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java (1)
api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java (1)
Getter(8-22)
security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java (1)
api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java (1)
Slf4j(37-170)
🔇 Additional comments (33)
application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java (1)
14-16: 스레드 풀 크기 설정 검토 필요현재 코어 풀 크기(5), 최대 풀 크기(10), 큐 용량(100)으로 설정되어 있습니다. S3 업로드 작업의 예상 부하와 시스템 리소스를 고려하여 이 값들이 적절한지 검토해보세요.
common/src/main/java/com/nowait/common/enums/Role.java (1)
5-6: SUPER_ADMIN 역할 추가가 올바르게 구현되었습니다.PR 목표에 명시된 슈퍼계정 생성 기능을 위한 SUPER_ADMIN 역할이 적절히 추가되었습니다. 기존 코드 구조와 일관성을 유지하면서 새로운 역할이 추가되었습니다.
domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java (1)
9-11: 표준적인 Spring Data JPA 리포지토리 구현입니다.코드가 Spring Data JPA 패턴을 올바르게 따르고 있으며, Optional을 사용하여 null 안전성을 제공합니다. 메서드 네이밍도 Spring Data JPA 규칙에 맞습니다.
security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java (1)
3-8: 예외 클래스 구현이 적절합니다BusinessException을 상속받은 토큰 관련 예외 클래스가 올바르게 구현되었습니다. 명확한 의미를 가진 에러 메시지를 사용하고 있습니다.
api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java (1)
9-18: 보안을 고려한 우수한 DTO 구현입니다민감한 토큰 정보를
@ToString에서 제외하여 로깅 시 토큰 노출을 방지한 것은 훌륭한 보안 고려사항입니다. 불변성도 잘 보장되어 있고 JSON 직렬화 설정도 적절합니다.application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java (1)
57-57: 반환 값의 일관성을 확인하세요.새로운
refreshToken대신 기존refreshToken을 반환하고 있습니다. 이것이 의도된 동작인지 확인이 필요합니다.- AuthenticationResponse authenticationResponse = new AuthenticationResponse(newAccessToken, refreshToken); + AuthenticationResponse authenticationResponse = new AuthenticationResponse(newAccessToken, newRefreshToken);이 변경이 필요한지 확인하세요. 일반적으로 새로 생성된 refresh token을 반환하는 것이 일반적입니다.
api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java (1)
9-21: 잘 구현된 DTO 클래스입니다.Lombok 어노테이션과 빌더 패턴을 적절히 사용하여 깔끔하게 구현되었습니다. 정적 팩토리 메서드
of()도 가독성을 높여줍니다.api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java (1)
9-15: 불변 객체 설계가 잘 적용된 DTO입니다.
final키워드를 사용하여 불변성을 보장하고, 명확한 주석과 함께 가독성이 좋습니다.security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java (1)
3-19: 잘 설계된 OAuth2 응답 인터페이스OAuth2 제공자별 응답 데이터를 추상화하는 인터페이스가 명확하게 정의되어 있습니다. 메서드명이 직관적이고 한국어 주석으로 각 필드의 용도가 잘 설명되어 있습니다.
api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java (1)
8-20: 일관된 DTO 패턴과 불변성 보장Builder 패턴과 final 필드를 사용하여 불변성을 보장하고, 정적 팩토리 메서드로 엔티티에서 DTO로의 변환을 깔끔하게 처리했습니다.
StoreImageUploadResponse와 동일한 패턴을 따라 일관성이 유지되고 있습니다.security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java (1)
3-14: 잘 구조화된 예외 클래스입니다.
ResourceNotFoundException은 다음과 같은 좋은 설계 패턴을 따르고 있습니다:
ErrorMessage를 통한 구조화된 오류 정보 관리- 추상 클래스로 설계되어 구체적인 예외 타입들의 기반 제공
getCode()메서드를 통한 오류 코드 접근 제공예외 계층 구조가 명확하고 유지보수가 용이합니다.
api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java (1)
9-24: 표준적인 DTO 패턴을 잘 따르고 있습니다.
BookmarkGetResponse는 다음과 같은 좋은 설계를 보여줍니다:
- Lombok을 활용한 보일러플레이트 코드 감소
fromEntity정적 팩토리 메서드를 통한 명확한 엔티티-DTO 변환- 빌더 패턴을 통한 객체 생성의 일관성
코드 구조가 깔끔하고 유지보수가 용이합니다.
api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java (1)
8-22: 잘 설계된 DTO 클래스입니다.
StoreImageUploadResponse는 다음과 같은 좋은 설계를 보여줍니다:
final필드를 통한 불변성 보장MenuImageUploadResponse와 일관된 패턴을 따름- 스토어 이미지 타입 구분을 위한
type필드 추가로 더 풍부한 정보 제공fromEntity정적 팩토리 메서드를 통한 명확한 엔티티-DTO 변환코드 구조가 깔끔하고 다른 이미지 업로드 응답 DTO들과 일관성을 유지합니다.
api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java (1)
25-25: 빈 주문 항목 리스트 초기화 검토 필요
fromEntity메서드에서orderItems를 빈 리스트로 초기화하고 있습니다. 실제 주문 항목 데이터를 포함해야 하는지 확인해주세요. 만약 의도적으로 빈 리스트를 반환하는 것이라면, 이에 대한 주석을 추가하는 것이 좋겠습니다.api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java (1)
14-32: 잘 구조화된 DTO 클래스메뉴 데이터를 위한 DTO 클래스가 잘 설계되었습니다. Lombok 어노테이션 사용과 정적 팩토리 메서드 패턴이 적절하게 적용되어 있습니다.
api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java (1)
26-27: 적절한 트랜잭션 설정과 서비스 구조읽기 전용 트랜잭션 설정이 적절하게 적용되어 있고, 메서드 시그니처와 반환 타입이 명확합니다.
api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java (1)
12-23: 페이지네이션을 고려한 좋은 응답 구조매장 목록 응답에
hasNext필드를 포함하여 페이지네이션을 지원하는 설계가 좋습니다. 정적 팩토리 메서드도 적절하게 구현되어 있습니다.api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java (1)
8-16: 간결하고 명확한 DTO 구조주문 항목에 필요한 핵심 정보를 포함하는 간결한 DTO입니다. Lombok 어노테이션 사용과 필드 구성이 적절하며, 다른 DTO들과 일관성 있는 패턴을 따르고 있습니다.
security-front/src/main/java/com/nowait/exception/UnauthorizedException.java (1)
3-19: 예외 클래스 구현이 올바르게 되어 있습니다.ErrorMessage를 활용한 일관된 에러 처리 구조와 불변성을 보장하는 final 필드 사용이 적절합니다. 두 개의 생성자를 통해 유연성도 확보되어 있습니다.
api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java (1)
10-29: DTO 구현이 잘 되어 있습니다.Lombok 어노테이션 활용과 정적 팩토리 메소드를 통한 엔티티 변환 로직이 명확하고 적절합니다. 필드 타입과 네이밍도 적절합니다.
security-front/src/main/java/com/nowait/exception/ErrorResponse.java (1)
8-26: 에러 응답 클래스 구현이 올바릅니다.final 필드를 사용한 불변성 보장과 두 개의 생성자를 통한 유연성 제공이 적절합니다. 상세 에러 정보를 위한 Map 필드도 잘 설계되어 있습니다.
security-front/src/main/java/com/nowait/exception/ErrorMessage.java (1)
6-22: 에러 메시지 열거형이 잘 구성되어 있습니다.카테고리별로 에러 메시지를 분류하고 일관된 코드 네이밍 규칙을 사용한 점이 좋습니다. 한국어 메시지와 영문 코드의 조합도 적절합니다.
api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java (1)
8-17: 잘 구현된 DTO 클래스입니다.표준적인 DTO 패턴을 따르고 있으며, Lombok 어노테이션을 적절히 활용하여 코드가 간결하고 읽기 쉽습니다. 예약 생성 응답에 필요한 모든 필드가 포함되어 있습니다.
api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java (1)
12-39: 잘 설계된 DTO 클래스입니다.이 클래스는 다음과 같은 장점을 가지고 있습니다:
- 적절한 Lombok 어노테이션 사용으로 코드가 간결함
- 정적 팩토리 메서드
fromEntity를 통해 엔티티에서 DTO로의 변환을 명확하게 처리- 매장 조회에 필요한 모든 필드가 포함되어 있음
- 이미지 리스트를 별도 DTO로 관리하여 관심사 분리가 잘 되어 있음
api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java (1)
15-18: 패키지 명명 불일치를 수정하세요.일부 import에서
com.nowaiting패키지를 사용하고 있지만, 다른 곳에서는com.nowait를 사용하고 있습니다. 일관성을 유지하세요.-import com.nowaiting.common.enums.ReservationStatus; -import com.nowaiting.user.entity.User; -import com.nowaiting.user.exception.UserNotFoundException; -import com.nowaiting.user.repository.UserRepository; +import com.nowait.common.enums.ReservationStatus; +import com.nowait.user.entity.User; +import com.nowait.user.exception.UserNotFoundException; +import com.nowait.user.repository.UserRepository;Likely an incorrect or invalid review comment.
security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java (1)
46-57: 만료 검증 로직 확인 필요
jwtUtil.isExpired()가 boolean 을 반환한다면 현재 결과를 무시하고 있습니다. 예외 기반/반환값 기반 중 한 가지로 통일해 주세요.security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java (1)
26-35: 액세스 토큰 생성 로직이 올바르게 구현되었습니다.토큰 카테고리, 사용자 ID, 역할 정보를 포함한 클레임 구조가 적절하며, 만료 시간 설정도 올바르게 구현되어 있습니다.
api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java (1)
27-45: 북마크 생성 로직이 올바르게 구현되었습니다.중복 북마크 검증, 엔티티 존재 확인, 트랜잭션 관리가 적절하게 구현되어 있습니다. 비즈니스 로직이 명확하고 예외 처리도 적절합니다.
api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java (1)
29-42: 북마크 생성 엔드포인트가 올바르게 구현되었습니다.REST 규칙을 따르는 경로 설계, 적절한 HTTP 상태 코드(201 Created), 표준화된 응답 형식이 잘 구현되어 있습니다.
api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java (2)
34-50: 주문 생성 엔드포인트가 올바르게 구현되었습니다.세션 기반 인증, 요청 DTO 검증, 적절한 HTTP 상태 코드 사용 등이 잘 구현되어 있습니다. 비로그인 사용자를 위한 세션 ID 활용 방식도 적절합니다.
52-69: 주문 아이템 조회 엔드포인트가 올바르게 구현되었습니다.세션 기반 주문 조회 로직이 적절하며, 테이블별 주문 격리도 잘 구현되어 있습니다.
api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java (1)
46-58: 단일 스토어 조회 로직이 올바르게 구현되었습니다.엔티티 존재 확인, 적절한 예외 처리, 이미지 포함 응답 생성이 잘 구현되어 있습니다.
api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java (1)
112-119: 상수 접근 방식 불일치다른 곳은
DUPLICATE_BOOKMARK를 정적 임포트로 쓰는데 여기만ErrorMessage.DUPLICATE_BOOKMARK로 직접 참조합니다. 스타일을 맞춰 주세요.
| @Value("${cloud.aws.credentials.access-key}") | ||
| private String accessKey; | ||
|
|
||
| @Value("${cloud.aws.credentials.secret-key}") | ||
| private String secretKey; | ||
|
|
||
| @Value("${cloud.aws.region.static}") | ||
| private String region; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
보안 취약점: 하드코딩된 AWS 자격증명 사용
현재 구성에서는 BasicAWSCredentials를 사용하여 액세스 키와 시크릿 키를 직접 설정하고 있습니다. 이는 보안상 위험할 수 있습니다.
더 안전한 방법을 고려해보세요:
- IAM 역할 사용 (EC2/ECS 환경에서)
- AWS 자격증명 프로필 사용
- 환경변수 기반 자격증명 제공자 사용
🤖 Prompt for AI Agents
In infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java
around lines 15 to 22, avoid hardcoding AWS access and secret keys using
BasicAWSCredentials due to security risks. Refactor the code to use a more
secure credential provider such as IAM roles if running on EC2/ECS, AWS
credentials profiles, or environment variable-based credential providers. Remove
direct injection of accessKey and secretKey fields and configure the AWS SDK
client to automatically retrieve credentials from these safer sources.
| BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); | ||
| return (AmazonS3Client)AmazonS3ClientBuilder.standard() | ||
| .withRegion(region) | ||
| .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) | ||
| .build(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
자격증명 유효성 검사 누락
자격증명이 null이거나 비어있는 경우에 대한 검증 로직이 없습니다. 잘못된 구성으로 인한 런타임 오류를 방지하기 위해 검증 로직을 추가하세요.
@Bean
public AmazonS3Client amazonS3Client() {
+ if (accessKey == null || accessKey.trim().isEmpty()) {
+ throw new IllegalArgumentException("AWS access key is not configured");
+ }
+ if (secretKey == null || secretKey.trim().isEmpty()) {
+ throw new IllegalArgumentException("AWS secret key is not configured");
+ }
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client)AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); | |
| return (AmazonS3Client)AmazonS3ClientBuilder.standard() | |
| .withRegion(region) | |
| .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) | |
| .build(); | |
| } | |
| @Bean | |
| public AmazonS3Client amazonS3Client() { | |
| if (accessKey == null || accessKey.trim().isEmpty()) { | |
| throw new IllegalArgumentException("AWS access key is not configured"); | |
| } | |
| if (secretKey == null || secretKey.trim().isEmpty()) { | |
| throw new IllegalArgumentException("AWS secret key is not configured"); | |
| } | |
| BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); | |
| return (AmazonS3Client) AmazonS3ClientBuilder.standard() | |
| .withRegion(region) | |
| .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) | |
| .build(); | |
| } |
🤖 Prompt for AI Agents
In infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java
around lines 26 to 31, there is no validation for the AWS credentials being null
or empty, which can cause runtime errors. Add validation checks for accessKey
and secretKey before creating BasicAWSCredentials, throwing an appropriate
exception or logging an error if either is null or empty to prevent
misconfiguration issues.
| private String createFileKey(String type, Long refId, String filename) { | ||
| return type + "/" + refId + "/" + UUID.randomUUID() + "-" + filename; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
파일 키 생성 로직 보안 검토
파일 키 생성 시 보안을 위해 다음 사항을 고려하세요:
- 파일명 검증 및 정화
- 경로 탐색 공격 방지
- 파일 확장자 검증
private String createFileKey(String type, Long refId, String filename) {
+ // 파일명 정화 및 검증
+ String sanitizedFilename = sanitizeFilename(filename);
+ validateFileExtension(sanitizedFilename);
return type + "/" + refId + "/" + UUID.randomUUID() + "-" + sanitizedFilename;
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java around
lines 54 to 56, the createFileKey method currently concatenates the filename
directly without validation, which risks security issues like path traversal and
unsafe filenames. To fix this, implement validation and sanitization of the
filename to remove or reject unsafe characters, ensure the filename does not
contain path traversal sequences like "../", and verify that the file extension
is allowed or expected. This will secure the file key generation against
injection or traversal attacks.
| try (InputStream inputStream = file.getInputStream()) { | ||
| String key = createFileKey(type, refId, file.getOriginalFilename()); | ||
| ObjectMetadata metadata = new ObjectMetadata(); | ||
| metadata.setContentLength(file.getSize()); | ||
|
|
||
| amazonS3Client.putObject(bucket, key, inputStream, metadata); | ||
| String url = amazonS3Client.getUrl(bucket, key).toString(); | ||
|
|
||
| return CompletableFuture.completedFuture(new S3UploadResult(key, url)); | ||
| } catch (Exception e) { | ||
| throw new RuntimeException("S3 업로드 실패", e); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
리소스 관리 및 예외 처리 개선
현재 try-with-resources를 사용하고 있지만, 더 구체적인 예외 처리와 로깅을 추가하는 것을 권장합니다:
@Bulkhead(name = "s3UploadBulkhead", type = Bulkhead.Type.THREADPOOL)
@Async("s3UploadExecutor")
public CompletableFuture<S3UploadResult> upload(String type, Long refId, MultipartFile file) {
try (InputStream inputStream = file.getInputStream()) {
+ validateUploadParameters(type, refId, file);
String key = createFileKey(type, refId, file.getOriginalFilename());
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
+ metadata.setContentType(file.getContentType());
amazonS3Client.putObject(bucket, key, inputStream, metadata);
String url = amazonS3Client.getUrl(bucket, key).toString();
return CompletableFuture.completedFuture(new S3UploadResult(key, url));
} catch (Exception e) {
+ log.error("S3 업로드 실패: type={}, refId={}, filename={}", type, refId, file.getOriginalFilename(), e);
throw new RuntimeException("S3 업로드 실패", e);
}
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java around
lines 32 to 44, improve resource management and exception handling by adding
more specific catch blocks for different exceptions and include logging of the
error details before throwing the RuntimeException. This will help in better
diagnosing issues during S3 upload failures while maintaining the
try-with-resources for InputStream management.
|
|
||
| @Bulkhead(name = "s3UploadBulkhead", type = Bulkhead.Type.THREADPOOL) | ||
| @Async("s3UploadExecutor") | ||
| public CompletableFuture<S3UploadResult> upload(String type, Long refId, MultipartFile file) { // TODO MultipartFile 분리 필요 (Spring에 의존하면 안 됨) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
메서드 파라미터 검증 누락
업로드 메서드에서 null 파라미터 검증이 없습니다. 방어적 프로그래밍을 위해 검증 로직을 추가하세요:
public CompletableFuture<S3UploadResult> upload(String type, Long refId, MultipartFile file) {
+ Objects.requireNonNull(type, "type cannot be null");
+ Objects.requireNonNull(refId, "refId cannot be null");
+ Objects.requireNonNull(file, "file cannot be null");
+
+ if (file.isEmpty()) {
+ throw new IllegalArgumentException("파일이 비어있습니다");
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public CompletableFuture<S3UploadResult> upload(String type, Long refId, MultipartFile file) { // TODO MultipartFile 분리 필요 (Spring에 의존하면 안 됨) | |
| public CompletableFuture<S3UploadResult> upload(String type, Long refId, MultipartFile file) { // TODO MultipartFile 분리 필요 (Spring에 의존하면 안 됨) | |
| Objects.requireNonNull(type, "type cannot be null"); | |
| Objects.requireNonNull(refId, "refId cannot be null"); | |
| Objects.requireNonNull(file, "file cannot be null"); | |
| if (file.isEmpty()) { | |
| throw new IllegalArgumentException("파일이 비어있습니다"); | |
| } | |
| // ... existing upload logic ... |
🤖 Prompt for AI Agents
In infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java at line
31, the upload method lacks null checks for its parameters. Add defensive null
validation for 'type', 'refId', and 'file' at the start of the method to prevent
potential NullPointerExceptions and ensure robustness.
security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java
Outdated
Show resolved
Hide resolved
security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java
Outdated
Show resolved
Hide resolved
| .authorizeHttpRequests(auth -> auth | ||
| .requestMatchers( | ||
| "/oauth2/authorization/kakao", // 카카오 로그인 요청 | ||
| "/login/oauth2/code/**", // 카카오 인증 콜백 | ||
| "/api/refresh-token", // refresh token (토큰 갱신) | ||
| "orders/**", // 주문 관련 API | ||
| "/v1/menus/**", | ||
| "/v1/stores/**", | ||
| "/api/users/signup", | ||
| "/api/users/login", | ||
| "/swagger-ui/**", | ||
| "/v3/api-docs/**", | ||
| "/v3/api-docs.json", | ||
| "/api-docs/**", | ||
| "/swagger-resources/**", | ||
| "/webjars/**", | ||
| "/demo-ui.html" | ||
| ) | ||
| .permitAll() | ||
| .anyRequest().authenticated() // 그외 요청은 허가된 사람만 인가 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ant 패턴 누락된 슬래시
"orders/**" 와 "v1/stores/**" 앞에 / 가 없어 매칭이 되지 않을 수 있습니다.
- "orders/**",
+ "/orders/**",같이 수정해 주세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .authorizeHttpRequests(auth -> auth | |
| .requestMatchers( | |
| "/oauth2/authorization/kakao", // 카카오 로그인 요청 | |
| "/login/oauth2/code/**", // 카카오 인증 콜백 | |
| "/api/refresh-token", // refresh token (토큰 갱신) | |
| "orders/**", // 주문 관련 API | |
| "/v1/menus/**", | |
| "/v1/stores/**", | |
| "/api/users/signup", | |
| "/api/users/login", | |
| "/swagger-ui/**", | |
| "/v3/api-docs/**", | |
| "/v3/api-docs.json", | |
| "/api-docs/**", | |
| "/swagger-resources/**", | |
| "/webjars/**", | |
| "/demo-ui.html" | |
| ) | |
| .permitAll() | |
| .anyRequest().authenticated() // 그외 요청은 허가된 사람만 인가 | |
| .authorizeHttpRequests(auth -> auth | |
| .requestMatchers( | |
| "/oauth2/authorization/kakao", // 카카오 로그인 요청 | |
| "/login/oauth2/code/**", // 카카오 인증 콜백 | |
| "/api/refresh-token", // refresh token (토큰 갱신) | |
| "/orders/**", // 주문 관련 API | |
| "/v1/menus/**", | |
| "/v1/stores/**", | |
| "/api/users/signup", | |
| "/api/users/login", | |
| "/swagger-ui/**", | |
| "/v3/api-docs/**", | |
| "/v3/api-docs.json", | |
| "/api-docs/**", | |
| "/swagger-resources/**", | |
| "/webjars/**", | |
| "/demo-ui.html" | |
| ) | |
| .permitAll() | |
| .anyRequest().authenticated() // 그외 요청은 허가된 사람만 인가 |
🤖 Prompt for AI Agents
In security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java
between lines 55 and 74, the Ant patterns "orders/**" and "v1/stores/**" are
missing leading slashes, which may cause them not to match correctly. Add a
leading slash to these patterns, changing them to "/orders/**" and
"/v1/stores/**" respectively, to ensure proper request matching.
| @Transactional | ||
| public Boolean validateToken(String token, Long userId){ | ||
| // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. | ||
| Optional<Token> savedToken = tokenRepository.findByUserId(userId); | ||
|
|
||
| // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 | ||
| if (savedToken.isEmpty()){ | ||
| log.info("여기에 걸렸니 ? -- 1 "); | ||
| return false; | ||
| } | ||
|
|
||
| // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 | ||
| if (!savedToken.get().getRefreshToken().equals(token)){ | ||
| log.info("여기에 걸렸니 ? -- 2 "); | ||
| return false; | ||
| } | ||
|
|
||
| // 리프레시 토큰의 만료여부 확인 | ||
| if(jwtUtil.isExpired(token)){ | ||
| log.info("여기에 걸렸니 ? -- 3 "); | ||
| return false; // 만료된 토큰은 유효하지 않음 | ||
| } | ||
|
|
||
| log.info("여기에 걸렸니 ? -- 4 "); | ||
| return true; // 모든 조건 만족 시, 유효한 토큰 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
불필요한 래퍼 타입 & 디버그 로그
Boolean 대신 boolean 을 사용하고, "여기에 걸렸니 ?" 식의 로그는 제거하거나 수준을 DEBUG 로 바꿔 주세요.
- public Boolean validateToken(String token, Long userId){
+ public boolean validateToken(String token, Long userId){
...
- log.info("여기에 걸렸니 ? -- 1 ");
+ log.debug("token mismatch for user {}", userId);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Transactional | |
| public Boolean validateToken(String token, Long userId){ | |
| // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. | |
| Optional<Token> savedToken = tokenRepository.findByUserId(userId); | |
| // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 | |
| if (savedToken.isEmpty()){ | |
| log.info("여기에 걸렸니 ? -- 1 "); | |
| return false; | |
| } | |
| // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 | |
| if (!savedToken.get().getRefreshToken().equals(token)){ | |
| log.info("여기에 걸렸니 ? -- 2 "); | |
| return false; | |
| } | |
| // 리프레시 토큰의 만료여부 확인 | |
| if(jwtUtil.isExpired(token)){ | |
| log.info("여기에 걸렸니 ? -- 3 "); | |
| return false; // 만료된 토큰은 유효하지 않음 | |
| } | |
| log.info("여기에 걸렸니 ? -- 4 "); | |
| return true; // 모든 조건 만족 시, 유효한 토큰 | |
| } | |
| @Transactional | |
| public boolean validateToken(String token, Long userId){ | |
| // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. | |
| Optional<Token> savedToken = tokenRepository.findByUserId(userId); | |
| // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 | |
| if (savedToken.isEmpty()){ | |
| log.debug("token mismatch for user {}", userId); | |
| return false; | |
| } | |
| // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 | |
| if (!savedToken.get().getRefreshToken().equals(token)){ | |
| log.info("여기에 걸렸니 ? -- 2 "); | |
| return false; | |
| } | |
| // 리프레시 토큰의 만료여부 확인 | |
| if (jwtUtil.isExpired(token)){ | |
| log.info("여기에 걸렸니 ? -- 3 "); | |
| return false; // 만료된 토큰은 유효하지 않음 | |
| } | |
| log.info("여기에 걸렸니 ? -- 4 "); | |
| return true; // 모든 조건 만족 시, 유효한 토큰 | |
| } |
🤖 Prompt for AI Agents
In api-user/src/main/java/com/example/apiuser/token/service/TokenService.java
between lines 25 and 50, replace the return type and variable type from the
wrapper class Boolean to the primitive boolean to avoid unnecessary boxing.
Also, remove the informal log messages containing "여기에 걸렸니 ?" or change their
log level to DEBUG to reduce noise in the logs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review continued from previous batch...
| // userId와 role 값 추출 | ||
| Long userId = jwtUtil.getUserId(accessToken); | ||
| String roleString = jwtUtil.getRole(accessToken); | ||
|
|
||
| User user = User.createUserWithId(userId, "sampleEmail", "sampleNickname", "sampleProfileImg" | ||
| , SocialType.KAKAO, Role.fromString(roleString)); | ||
|
|
||
| CustomOAuth2User customOAuth2User = new CustomOAuth2User(user); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
임시 사용자 정보 하드코딩
sampleEmail 등 하드코딩 값은 감사 로그와 문제 추적을 어렵게 합니다. DB 조회 또는 캐시를 사용해 실제 사용자 정보를 로드해 주세요.
🤖 Prompt for AI Agents
In security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java
around lines 71 to 79, the user information is currently hardcoded with
placeholder values like "sampleEmail" which hinders audit logging and issue
tracking. Replace these hardcoded values by fetching the actual user details
from the database or a cache using the extracted userId. This ensures that the
CustomOAuth2User is created with real user data for accurate logging and
tracing.
| public String getTokenCategory(String token) { | ||
| return Jwts.parser().verifyWith(secretKey).build() | ||
| .parseClaimsJws(token) | ||
| .getBody() | ||
| .get("tokenCategory", String.class); | ||
| } | ||
|
|
||
| public String getRole(String token) { | ||
| return Jwts.parser().verifyWith(secretKey).build() | ||
| .parseClaimsJws(token) | ||
| .getBody() | ||
| .get("role", String.class); | ||
| } | ||
|
|
||
| public Long getUserId(String token) { | ||
| return Jwts.parser().verifyWith(secretKey).build() | ||
| .parseClaimsJws(token) | ||
| .getBody() | ||
| .get("userId", Long.class); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
토큰 파싱 메서드들에서 예외 처리가 누락되었습니다.
getTokenCategory, getRole, getUserId 메서드들에서 잘못된 토큰 형식이나 서명 검증 실패 시 발생할 수 있는 JwtException에 대한 처리가 없습니다. 이는 애플리케이션 전체에 영향을 줄 수 있는 런타임 예외로 이어질 수 있습니다.
public String getTokenCategory(String token) {
- return Jwts.parser().verifyWith(secretKey).build()
- .parseClaimsJws(token)
- .getBody()
- .get("tokenCategory", String.class);
+ try {
+ return Jwts.parser().verifyWith(secretKey).build()
+ .parseClaimsJws(token)
+ .getBody()
+ .get("tokenCategory", String.class);
+ } catch (JwtException e) {
+ throw new IllegalArgumentException("Invalid token", e);
+ }
}다른 파싱 메서드들에도 동일한 예외 처리를 적용해야 합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public String getTokenCategory(String token) { | |
| return Jwts.parser().verifyWith(secretKey).build() | |
| .parseClaimsJws(token) | |
| .getBody() | |
| .get("tokenCategory", String.class); | |
| } | |
| public String getRole(String token) { | |
| return Jwts.parser().verifyWith(secretKey).build() | |
| .parseClaimsJws(token) | |
| .getBody() | |
| .get("role", String.class); | |
| } | |
| public Long getUserId(String token) { | |
| return Jwts.parser().verifyWith(secretKey).build() | |
| .parseClaimsJws(token) | |
| .getBody() | |
| .get("userId", Long.class); | |
| } | |
| public String getTokenCategory(String token) { | |
| try { | |
| return Jwts.parser().verifyWith(secretKey).build() | |
| .parseClaimsJws(token) | |
| .getBody() | |
| .get("tokenCategory", String.class); | |
| } catch (JwtException e) { | |
| throw new IllegalArgumentException("Invalid token", e); | |
| } | |
| } |
🤖 Prompt for AI Agents
In security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java between lines
47 and 66, the methods getTokenCategory, getRole, and getUserId lack exception
handling for JwtException that may occur due to invalid token format or
signature verification failure. To fix this, wrap the token parsing logic in
each method with a try-catch block that catches JwtException and handles it
appropriately, such as logging the error and returning null or a default value.
Apply the same exception handling pattern to other token parsing methods in the
class to ensure consistent and safe error management.
| public Boolean isExpired(String token) { | ||
| return Jwts.parser().verifyWith(secretKey).build() | ||
| .parseSignedClaims(token) | ||
| .getPayload() | ||
| .getExpiration() | ||
| .before(new Date()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
토큰 만료 검증 메서드에서 예외 처리가 누락되었습니다.
isExpired 메서드에서 잘못된 토큰이나 만료된 토큰을 파싱할 때 발생할 수 있는 예외(JwtException, ExpiredJwtException 등)에 대한 처리가 없습니다. 이로 인해 런타임 예외가 발생할 수 있습니다.
public Boolean isExpired(String token) {
- return Jwts.parser().verifyWith(secretKey).build()
- .parseSignedClaims(token)
- .getPayload()
- .getExpiration()
- .before(new Date());
+ try {
+ return Jwts.parser().verifyWith(secretKey).build()
+ .parseSignedClaims(token)
+ .getPayload()
+ .getExpiration()
+ .before(new Date());
+ } catch (JwtException e) {
+ return true; // 토큰이 유효하지 않으면 만료된 것으로 간주
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public Boolean isExpired(String token) { | |
| return Jwts.parser().verifyWith(secretKey).build() | |
| .parseSignedClaims(token) | |
| .getPayload() | |
| .getExpiration() | |
| .before(new Date()); | |
| } | |
| public Boolean isExpired(String token) { | |
| try { | |
| return Jwts.parser().verifyWith(secretKey).build() | |
| .parseSignedClaims(token) | |
| .getPayload() | |
| .getExpiration() | |
| .before(new Date()); | |
| } catch (JwtException e) { | |
| return true; // 토큰이 유효하지 않으면 만료된 것으로 간주 | |
| } | |
| } |
🤖 Prompt for AI Agents
In security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java around lines 68
to 74, the isExpired method lacks exception handling for invalid or expired
tokens, which can cause runtime exceptions. Wrap the token parsing logic in a
try-catch block to catch JwtException and ExpiredJwtException, and return a safe
default value (e.g., true for expired) when exceptions occur to prevent runtime
errors.
| private static void parameterValidation(Long storeId, CustomOAuth2User customOAuth2User) { | ||
| // 파라미터 유효성 검사 | ||
| if (storeId == null || storeId < 0) { | ||
| throw new IllegalArgumentException("storeId must be a positive number"); | ||
| } | ||
| if (customOAuth2User == null || customOAuth2User.getUserId() == null) { | ||
| throw new IllegalArgumentException("UserInfo is required"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
매개변수 검증 로직을 개선할 수 있습니다.
현재 검증 로직은 중복 코드를 포함하고 있으며, 메서드명이 용도를 명확히 나타내지 않습니다. 또한 storeId가 0인 경우에 대한 처리가 애매합니다.
-private static void parameterValidation(Long storeId, CustomOAuth2User customOAuth2User) {
+private static void validateParameters(Long id, CustomOAuth2User customOAuth2User) {
// 파라미터 유효성 검사
- if (storeId == null || storeId < 0) {
- throw new IllegalArgumentException("storeId must be a positive number");
+ if (id == null || id <= 0) {
+ throw new IllegalArgumentException("ID must be a positive number");
}
if (customOAuth2User == null || customOAuth2User.getUserId() == null) {
throw new IllegalArgumentException("UserInfo is required");
}
}메서드 호출 부분도 업데이트해야 합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private static void parameterValidation(Long storeId, CustomOAuth2User customOAuth2User) { | |
| // 파라미터 유효성 검사 | |
| if (storeId == null || storeId < 0) { | |
| throw new IllegalArgumentException("storeId must be a positive number"); | |
| } | |
| if (customOAuth2User == null || customOAuth2User.getUserId() == null) { | |
| throw new IllegalArgumentException("UserInfo is required"); | |
| } | |
| } | |
| private static void validateParameters(Long id, CustomOAuth2User customOAuth2User) { | |
| // 파라미터 유효성 검사 | |
| if (id == null || id <= 0) { | |
| throw new IllegalArgumentException("ID must be a positive number"); | |
| } | |
| if (customOAuth2User == null || customOAuth2User.getUserId() == null) { | |
| throw new IllegalArgumentException("UserInfo is required"); | |
| } | |
| } |
🤖 Prompt for AI Agents
In
api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java
around lines 69 to 77, the parameterValidation method has redundant checks and
an unclear method name, and it does not clearly handle the case when storeId is
zero. Rename the method to clearly reflect its purpose, refactor the validation
logic to avoid duplication by separating checks or using helper methods, and
explicitly define whether storeId zero is valid or invalid (adjust the condition
accordingly). After refactoring, update all calls to this method to use the new
method name and signature if changed.
| if (bookmark.getUser().getId() != customOAuth2User.getUserId()) { | ||
| throw new IllegalArgumentException("you can only delete your own bookmark"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사용자 권한 검증에서 타입 안전성 문제가 있습니다.
!= 연산자를 사용한 Long 타입 비교는 객체 참조 비교가 될 수 있어 예상치 못한 결과를 초래할 수 있습니다. equals() 메서드나 Objects.equals()를 사용해야 합니다.
-if (bookmark.getUser().getId() != customOAuth2User.getUserId()) {
+if (!Objects.equals(bookmark.getUser().getId(), customOAuth2User.getUserId())) {
throw new IllegalArgumentException("you can only delete your own bookmark");
}파일 상단에 java.util.Objects import를 추가해야 합니다.
🤖 Prompt for AI Agents
In
api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java
around lines 62 to 64, the code uses '!=' to compare Long objects for user ID
equality, which can cause incorrect results due to reference comparison. Replace
the '!=' operator with 'Objects.equals()' to safely compare the Long values.
Also, add an import statement for 'java.util.Objects' at the top of the file to
use this method.
| List<StoreReadDto> storeRead = stores.stream() | ||
| .map(store -> { | ||
| List<StoreImage> images = storeImageRepository.findByStore(store); | ||
| List<StoreImageUploadResponse> imageDto = images.stream() | ||
| .map(StoreImageUploadResponse::fromEntity) | ||
| .toList(); | ||
| return StoreReadDto.fromEntity(store, imageDto); | ||
| }) | ||
| .toList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
N+1 쿼리 문제가 발생할 수 있습니다.
각 스토어별로 이미지를 개별적으로 조회하는 방식은 성능 문제를 야기할 수 있습니다. 스토어가 많을 경우 데이터베이스 쿼리 수가 급격히 증가합니다.
스토어와 이미지를 한 번에 조회하는 방식으로 개선을 고려해보세요:
@Override
@Transactional(readOnly = true)
public StoreReadResponse getAllStores() {
List<Store> stores = storeRepository.findAllByDeletedFalseWithImages();
List<StoreReadDto> storeRead = stores.stream()
.map(store -> {
List<StoreImageUploadResponse> imageDto = store.getImages().stream()
.map(StoreImageUploadResponse::fromEntity)
.toList();
return StoreReadDto.fromEntity(store, imageDto);
})
.toList();
return StoreReadResponse.of(storeRead, false);
}Repository에서 fetch join을 사용하여 한 번에 조회하는 메서드 추가가 필요합니다.
🤖 Prompt for AI Agents
In
api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java
around lines 31 to 39, the current code fetches store images individually for
each store causing an N+1 query problem. To fix this, modify the repository to
add a method that fetches stores with their images using a fetch join in a
single query. Then update the service method to use this new repository method
to retrieve stores along with their images together, and map the images from the
store entity instead of querying separately.
| return stores.stream() | ||
| .map(store -> { | ||
| List<StoreImage> images = storeImageRepository.findByStore(store); | ||
| List<StoreImageUploadResponse> imageDto = images.stream() | ||
| .map(StoreImageUploadResponse::fromEntity) | ||
| .toList(); | ||
| return StoreReadDto.fromEntity(store, imageDto); | ||
| }) | ||
| .toList(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
검색 기능에서도 N+1 쿼리 문제가 발생합니다.
searchStoresByName 메서드에서도 동일한 N+1 쿼리 문제가 존재합니다.
검색 시에도 이미지를 함께 조회하는 방식으로 개선이 필요합니다:
@Override
public List<StoreReadDto> searchStoresByName(String name) {
List<Store> stores = storeRepository.findByNameContainingIgnoreCaseAndDeletedFalseWithImages(name);
return stores.stream()
.map(store -> {
List<StoreImageUploadResponse> imageDto = store.getImages().stream()
.map(StoreImageUploadResponse::fromEntity)
.toList();
return StoreReadDto.fromEntity(store, imageDto);
})
.toList();
}🤖 Prompt for AI Agents
In
api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java
around lines 63 to 72, the searchStoresByName method suffers from an N+1 query
problem when fetching store images. To fix this, modify the repository method to
fetch stores with their images eagerly (e.g.,
findByNameContainingIgnoreCaseAndDeletedFalseWithImages), then in the service
method, map each store's images directly from the store entity instead of
querying the repository again. This eliminates multiple queries and improves
performance.
| boolean hasNext = false; | ||
|
|
||
| return StoreReadResponse.of(storeRead, hasNext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
페이지네이션 로직이 하드코딩되어 있습니다.
hasNext 변수가 항상 false로 하드코딩되어 있어 실제 페이지네이션 기능이 작동하지 않습니다.
- boolean hasNext = false;
+ // TODO: 실제 페이지네이션 로직 구현 필요
+ boolean hasNext = false; // 임시로 false 설정또는 페이지네이션을 지원하는 적절한 로직으로 개선해야 합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| boolean hasNext = false; | |
| return StoreReadResponse.of(storeRead, hasNext); | |
| // TODO: 실제 페이지네이션 로직 구현 필요 | |
| boolean hasNext = false; // 임시로 false 설정 | |
| return StoreReadResponse.of(storeRead, hasNext); |
🤖 Prompt for AI Agents
In
api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java
around lines 41 to 43, the hasNext variable is hardcoded to false, disabling
pagination functionality. Modify the code to calculate hasNext based on whether
there are more store records beyond the current page, such as by comparing the
total number of stores or checking if the current page size matches the
requested page size. Update the return statement to use this dynamic hasNext
value to properly support pagination.
| // 💡 [중복 주문 방지] signature 생성 및 체크 | ||
| String signature = generateOrderSignature(storeId, tableId, orderCreateRequestDto.getItems()); | ||
| checkDuplicateOrderSignature(signature); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
중복 주문 체크는 DB 레벨 보장이 필요합니다
checkDuplicateOrderSignature() 호출 전후로 별도 동시성 제어가 없어 동일 서명이 거의 동시에 INSERT될 경우 레이스 컨디션이 발생할 수 있습니다.
signature 컬럼에 UNIQUE 인덱스를 두고 DB 예외를 DuplicateOrderException으로 매핑하는 방식을 추가해 주세요.
🤖 Prompt for AI Agents
In api-user/src/main/java/com/example/apiuser/order/service/OrderService.java
around lines 44 to 47, the current duplicate order check using
checkDuplicateOrderSignature() lacks concurrency control, risking race
conditions on simultaneous inserts. To fix this, add a UNIQUE index on the
signature column in the database schema, then remove or supplement the in-memory
check by catching the database unique constraint violation exception during
insert and map it to a DuplicateOrderException to ensure DB-level enforcement of
uniqueness.
- jwt 토큰 에러 로그 구체화 - bookmark 참조비교에서 값비교로 변경
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
application-admin/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java (1)
26-50: 메서드 로직 검토 및 개선 제안.전반적인 토큰 검증 로직은 올바르지만, 다음 사항들을 고려해보시기 바랍니다:
jwtUtil.isExpired(token)호출 시 발생할 수 있는 예외 처리- 토큰 검증 실패 시 보안 이벤트 로깅 고려
- 메서드에 대한 상세한 JavaDoc 문서화
+ /** + * 리프레시 토큰의 유효성을 검증합니다. + * @param token 검증할 리프레시 토큰 + * @param userId 토큰 소유자 ID + * @return 토큰이 유효한 경우 true, 그렇지 않으면 false + */ public boolean validateToken(String token, Long userId){
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
application-admin/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java(1 hunks)application-user/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java(2 hunks)security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java(1 hunks)security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java(1 hunks)security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java
- security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java
- security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java
🔇 Additional comments (5)
application-user/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java (2)
4-4: Objects 임포트 추가가 적절합니다.Line 63에서 Objects.equals() 메서드 사용을 위해 필요한 임포트입니다.
63-63: Objects.equals()로 개선된 null-safe 비교 로직입니다.기존의 != 비교를 Objects.equals()로 변경하여 다음과 같은 이점을 얻었습니다:
- Long 타입 객체의 안전한 비교
- null 값 처리 시 NullPointerException 방지
- 더 견고한 소유권 검증 로직
application-admin/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java (3)
26-26: 반환 타입 개선이 좋습니다.
Boolean에서boolean으로 변경한 것은 좋은 개선사항입니다. 원시 타입 사용으로 메모리 효율성이 향상되고 오토박싱/언박싱 오버헤드가 제거됩니다.
32-32: 로깅 개선이 적절합니다.유효성 검증 실패 시
debug레벨로 변경하고 더 구체적인 메시지를 추가한 것은 좋은 개선사항입니다. 운영 환경에서의 로그 노이즈를 줄이면서도 디버깅 시 유용한 정보를 제공합니다.Also applies to: 38-38, 44-44
48-48: 성공 로깅 메시지 개선이 좋습니다.성공 케이스의 로그 메시지를 더 명확하게 개선한 것이 좋습니다.
info레벨은 성공적인 토큰 검증에 적절합니다.
작업 요약
Issue Link
문제점 및 어려움
해결 방안
Reference
Summary by CodeRabbit
신규 기능
버그 수정
문서화
환경 및 설정