-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 코스 선택 기능 구현 #48
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
Conversation
Walkthrough코스 즐겨찾기(Like) 기능이 구현되었습니다. 코스와 코스 컴포넌트 엔티티, 매퍼, 레포지토리, 서비스 계층이 확장 및 추가되었고, Redis 캐시에서 임시 코스 데이터를 불러와 영속화하는 로직이 포함되었습니다. 날짜 포맷 일관성 및 JSON 직렬화 설정도 변경되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Controller
participant Service
participant Redis
participant Repository
participant DB
Client->>Controller: POST /course/like/{courseId}
Controller->>Service: likeCourse(courseId)
Service->>Redis: Get "tempCourse:{courseId}"
Redis-->>Service: 임시 코스 데이터(JSON)
Service->>Service: JSON → CourseComponentDto 리스트 변환
Service->>Repository: User, TourSpot, Course, CourseComponent 조회/생성
Service->>DB: Course/Component 저장
Service->>Controller: 완료 응답(ApiResponse<Void>)
Controller-->>Client: 200 OK
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes해당 변경사항 중 요구사항과 무관한 기능적 변경은 발견되지 않았습니다. Possibly related PRs
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
✨ Finishing Touches
🧪 Generate unit tests
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. 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: 8
🧹 Nitpick comments (5)
src/main/java/com/opendata/domain/course/controller/CourseController.java (1)
35-41: 코스 좋아요 엔드포인트 구현이 적절합니다.RESTful 설계 원칙을 잘 따르고 있으며, 경로 변수 사용과 응답 타입이 적절합니다. 코스 선택 기능의 핵심 요구사항을 잘 구현했습니다.
다만
courseId파라미터에 대한 검증 로직(예: null/empty 체크, 형식 검증) 추가를 고려해보세요.src/main/java/com/opendata/domain/course/dto/response/CourseComponentDto.java (1)
13-17: 필드 명명 규칙 일관성 문제
tourSpotName은 camelCase를 사용하는 반면tourspotId는 모두 소문자로 되어 있습니다. 일관성을 위해tourspotId를tourSpotId로 변경하는 것이 좋겠습니다.- Long tourspotId, + Long tourSpotId,src/main/java/com/opendata/domain/course/entity/Course.java (2)
3-3: 사용하지 않는 import 제거 필요
JsonFormatimport가 사용되지 않고 있습니다.-import com.fasterxml.jackson.annotation.JsonFormat;
42-48: 날짜 할당 메서드에 유효성 검증 추가 권장시작 시간과 종료 시간의 유효성을 검증하면 데이터 무결성을 향상시킬 수 있습니다.
public void assignStartDtm(LocalDateTime startDtm){ + if (startDtm == null) { + throw new IllegalArgumentException("시작 시간은 null일 수 없습니다."); + } + if (this.endDtm != null && startDtm.isAfter(this.endDtm)) { + throw new IllegalArgumentException("시작 시간은 종료 시간보다 이후일 수 없습니다."); + } this.startDtm = startDtm; } public void assignEndDtm(LocalDateTime endDtm){ + if (endDtm == null) { + throw new IllegalArgumentException("종료 시간은 null일 수 없습니다."); + } + if (this.startDtm != null && endDtm.isBefore(this.startDtm)) { + throw new IllegalArgumentException("종료 시간은 시작 시간보다 이전일 수 없습니다."); + } this.endDtm = endDtm; }src/main/java/com/opendata/domain/course/service/CourseService.java (1)
232-244: 코스 컴포넌트 저장 로직 개선 가능반복문 내에서 매번 save를 호출하는 것보다 배치 저장을 사용하면 성능이 향상됩니다.
+ List<CourseComponent> courseComponents = new ArrayList<>(); for(int i = 0; i < tempCourse.size(); i++){ CourseComponentDto courseComponentDto = tempCourse.get(i); if (i == 0){ course.assignStartDtm(DateUtil.parseTime(courseComponentDto.time())); } if (i == tempCourse.size()-1){ course.assignEndDtm(DateUtil.parseTime(courseComponentDto.time())); } TourSpot tourSpot = tourSpotRepository.findById(courseComponentDto.tourspotId()).orElseThrow(); CourseComponent courseComponent = courseComponentMapper.toEntity(tourSpot, DateUtil.parseTime(courseComponentDto.time()), course); - - courseComponentRepository.save(courseComponent); + courseComponents.add(courseComponent); } + courseComponentRepository.saveAll(courseComponents);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
src/main/java/com/opendata/docs/CourseControllerDocs.java(2 hunks)src/main/java/com/opendata/domain/course/controller/CourseController.java(1 hunks)src/main/java/com/opendata/domain/course/dto/response/CourseComponentDto.java(1 hunks)src/main/java/com/opendata/domain/course/entity/Course.java(2 hunks)src/main/java/com/opendata/domain/course/entity/CourseComponent.java(2 hunks)src/main/java/com/opendata/domain/course/mapper/CourseComponentMapper.java(1 hunks)src/main/java/com/opendata/domain/course/mapper/CourseMapper.java(2 hunks)src/main/java/com/opendata/domain/course/repository/CourseComponentRepository.java(1 hunks)src/main/java/com/opendata/domain/course/service/CourseService.java(6 hunks)src/main/java/com/opendata/domain/user/entity/User.java(2 hunks)src/main/java/com/opendata/global/config/RedisConfig.java(2 hunks)src/main/java/com/opendata/global/config/SecurityConfig.java(1 hunks)src/main/java/com/opendata/global/response/ApiResponse.java(1 hunks)src/main/java/com/opendata/global/util/DateUtil.java(2 hunks)src/main/resources/application.yml(1 hunks)
🔇 Additional comments (14)
src/main/resources/application.yml (1)
19-22: 일관된 날짜 형식 설정이 잘 구현되었습니다.Jackson 설정을 통해 전역적으로 날짜 형식을
yyyy-MM-dd HH:mm:ss로 통일하고 타임스탬프 직렬화를 비활성화한 것은 좋은 접근입니다. 이는 DateUtil의 변경사항과 일치하며 애플리케이션 전반에서 일관된 날짜 처리를 보장합니다.src/main/java/com/opendata/global/util/DateUtil.java (1)
34-36: 새로운 parseTime 메서드가 잘 구현되었습니다.날짜 문자열을 LocalDateTime으로 파싱하는 메서드가 추가되어 직렬화/역직렬화 과정이 완성되었습니다. 일관된 형식 사용으로 안정적인 날짜 처리가 가능할 것입니다.
src/main/java/com/opendata/global/response/ApiResponse.java (1)
26-28: Void 성공 응답을 위한 유틸리티 메서드가 잘 추가되었습니다.데이터 없이 성공 상태만 반환해야 하는 엔드포인트를 위한
onSuccessVoid()메서드가 적절히 구현되었습니다. 코스 좋아요 기능과 같이 단순한 성공/실패만 알려주면 되는 API에 유용할 것입니다.src/main/java/com/opendata/domain/course/repository/CourseComponentRepository.java (1)
1-9: 표준 JPA 레포지토리 구현이 올바릅니다.Spring Data JPA 규칙을 잘 따르고 있으며, 기본 CRUD 작업에 필요한 모든 설정이 적절히 구성되어 있습니다.
src/main/java/com/opendata/domain/course/entity/CourseComponent.java (4)
11-11: 테이블 명시적 매핑이 적절합니다.
@Table(name = "course_component")로 테이블 이름을 명시적으로 지정하여 데이터베이스 매핑이 명확해졌습니다.
22-24: Course와의 관계 설정이 올바릅니다.
@ManyToOne관계가 적절히 구성되었으며, LAZY 로딩과 조인 컬럼 설정이 정확합니다. 이는 코스 선택 기능 구현에 필요한 핵심 관계입니다.
12-15: Lombok 어노테이션 변경이 적절합니다.
@Builder제거 및@Setter추가로 엔티티 조작이 더 유연해졌습니다. 생성자 어노테이션도 적절히 설정되어 있습니다.
34-36: 명시적 관계 설정 메서드가 유용합니다.
assignCourse메서드를 통해 Course 관계를 명시적으로 설정할 수 있어 코드의 가독성과 의도가 명확해졌습니다.src/main/java/com/opendata/global/config/RedisConfig.java (2)
81-83: 매개변수 이름 변경이 일관성을 높입니다.
redisConnectionFactory에서connectionFactory로 변경하여 메서드 매개변수 명명이 더 간결해졌습니다.
85-87: 날짜/시간 직렬화 설정이 개선되었습니다.
JavaTimeModule을 명시적으로 등록하고WRITE_DATES_AS_TIMESTAMPS를 비활성화하여 날짜/시간 처리의 일관성이 향상되었습니다.findAndRegisterModules()대신 명시적 등록을 사용하는 것이 더 예측 가능합니다.src/main/java/com/opendata/domain/course/mapper/CourseMapper.java (1)
16-16: toEntity(CourseComponentDto) 사용 위치 확인 필요코드베이스에서
CourseMapper.toEntity(CourseComponentDto)호출을 찾을 수 없습니다.CourseComponentDto를Course로 변환하는 것은 의미상 부자연스러워 보이므로, 아래 사항을 검토해 주세요:
- 서비스·컨트롤러 레이어에서 해당 메서드가 실제로 사용되는지 확인
- 사용하지 않는다면
CourseMapper에서 해당 메서드를 제거하거나
CourseComponentDto → CourseComponent매핑은CourseComponentMapper로 이동- 설계 상
CourseComponentDto를Course로 매핑해야 하는 특별한 이유가 있다면, 주석 또는 문서로 명확히 설명대상 파일:
- src/main/java/com/opendata/domain/course/mapper/CourseMapper.java
src/main/java/com/opendata/docs/CourseControllerDocs.java (1)
68-92: API 문서화가 적절히 구현되었습니다.좋아요 등록 API의 문서화가 명확하고 RESTful 규칙을 잘 따르고 있습니다. PathVariable을 사용한 단순화된 엔드포인트 설계가 좋습니다.
src/main/java/com/opendata/domain/course/mapper/CourseComponentMapper.java (1)
1-36: MapStruct 매퍼 구현이 적절합니다.양방향 관계 설정을 위한
@AfterMapping사용과@Context를 통한 Course 전달이 잘 구현되었습니다. Spring 컴포넌트 모델 사용도 적절합니다.src/main/java/com/opendata/domain/course/service/CourseService.java (1)
150-150: CourseComponentMapper 사용이 적절합니다.CourseMapper를 CourseComponentMapper로 교체한 것이 도메인 모델에 더 적합합니다.
Also applies to: 161-161
| return new CourseComponentDto(tourSpot.getTourspotNm(), tourSpot.getTourspotId(), level.getCongestionLabel() ,c.getTourspotTm().toString(), | ||
| tourSpot.getAddress().getLatitude(), tourSpot.getAddress().getLongitude()); |
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.
날짜 형식 변환 시 포맷터 사용 필요
c.getTourspotTm().toString()은 LocalDateTime의 기본 toString() 메서드를 사용하는데, 이는 ISO-8601 형식을 반환합니다. @JsonFormat 어노테이션에 지정된 형식과 일치하도록 명시적인 포맷터를 사용해야 합니다.
- return new CourseComponentDto(tourSpot.getTourspotNm(), tourSpot.getTourspotId(), level.getCongestionLabel() ,c.getTourspotTm().toString(),
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ return new CourseComponentDto(tourSpot.getTourspotNm(), tourSpot.getTourspotId(), level.getCongestionLabel(), c.getTourspotTm().format(formatter),📝 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.
| return new CourseComponentDto(tourSpot.getTourspotNm(), tourSpot.getTourspotId(), level.getCongestionLabel() ,c.getTourspotTm().toString(), | |
| tourSpot.getAddress().getLatitude(), tourSpot.getAddress().getLongitude()); | |
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | |
| return new CourseComponentDto(tourSpot.getTourspotNm(), tourSpot.getTourspotId(), level.getCongestionLabel(), c.getTourspotTm().format(formatter), | |
| tourSpot.getAddress().getLatitude(), tourSpot.getAddress().getLongitude()); |
🤖 Prompt for AI Agents
In src/main/java/com/opendata/domain/course/dto/response/CourseComponentDto.java
around lines 20 to 21, the code uses c.getTourspotTm().toString() which returns
the LocalDateTime in ISO-8601 format by default. To match the date format
specified by the @JsonFormat annotation, replace the toString() call with a
DateTimeFormatter that formats the LocalDateTime explicitly according to the
desired pattern before returning it.
| @Transactional | ||
| public void likeCourse(String courseId) { | ||
| User user = userRepository.findById(1L).orElseThrow(); | ||
| ObjectMapper objectMapper = new ObjectMapper(); | ||
|
|
||
| List<?> rawList = (List<?>) redisTemplate.opsForValue().get("tempCourse:" + courseId); | ||
|
|
||
| List<CourseComponentDto> tempCourse = objectMapper.convertValue( | ||
| rawList, | ||
| new TypeReference<List<CourseComponentDto>>() {} | ||
| ); | ||
|
|
||
| tempCourse.forEach( | ||
| tc ->{ | ||
| System.out.println(tc.tourspotId()); | ||
| System.out.println(tc.tourSpotName()); | ||
| } | ||
| ); | ||
| Course course = Course.builder() | ||
| .user(user) | ||
| .build(); | ||
|
|
||
| courseRepository.save(course); | ||
| for(int i = 0; i < tempCourse.size(); i++){ | ||
| CourseComponentDto courseComponentDto = tempCourse.get(i); | ||
| if (i == 0){ | ||
| course.assignStartDtm(DateUtil.parseTime(courseComponentDto.time())); | ||
| } | ||
| if (i == tempCourse.size()-1){ | ||
| course.assignEndDtm(DateUtil.parseTime(courseComponentDto.time())); | ||
| } | ||
| TourSpot tourSpot = tourSpotRepository.findById(courseComponentDto.tourspotId()).orElseThrow(); | ||
| CourseComponent courseComponent = courseComponentMapper.toEntity(tourSpot, DateUtil.parseTime(courseComponentDto.time()), course); | ||
|
|
||
| courseComponentRepository.save(courseComponent); | ||
| } | ||
| } |
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
트랜잭션 롤백 시나리오 고려 필요
@Transactional 어노테이션이 있지만, 예외 발생 시 Redis 데이터가 이미 삭제되었을 수 있습니다. Redis 데이터 삭제는 트랜잭션이 성공적으로 완료된 후에 수행하는 것이 안전합니다.
Redis에서 데이터를 가져온 후 바로 삭제하지 말고, 모든 데이터베이스 작업이 성공적으로 완료된 후에 삭제하는 것을 권장합니다.
+ // 트랜잭션 성공 후 Redis 데이터 삭제
+ redisTemplate.delete("tempCourse:" + courseId);📝 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 void likeCourse(String courseId) { | |
| User user = userRepository.findById(1L).orElseThrow(); | |
| ObjectMapper objectMapper = new ObjectMapper(); | |
| List<?> rawList = (List<?>) redisTemplate.opsForValue().get("tempCourse:" + courseId); | |
| List<CourseComponentDto> tempCourse = objectMapper.convertValue( | |
| rawList, | |
| new TypeReference<List<CourseComponentDto>>() {} | |
| ); | |
| tempCourse.forEach( | |
| tc ->{ | |
| System.out.println(tc.tourspotId()); | |
| System.out.println(tc.tourSpotName()); | |
| } | |
| ); | |
| Course course = Course.builder() | |
| .user(user) | |
| .build(); | |
| courseRepository.save(course); | |
| for(int i = 0; i < tempCourse.size(); i++){ | |
| CourseComponentDto courseComponentDto = tempCourse.get(i); | |
| if (i == 0){ | |
| course.assignStartDtm(DateUtil.parseTime(courseComponentDto.time())); | |
| } | |
| if (i == tempCourse.size()-1){ | |
| course.assignEndDtm(DateUtil.parseTime(courseComponentDto.time())); | |
| } | |
| TourSpot tourSpot = tourSpotRepository.findById(courseComponentDto.tourspotId()).orElseThrow(); | |
| CourseComponent courseComponent = courseComponentMapper.toEntity(tourSpot, DateUtil.parseTime(courseComponentDto.time()), course); | |
| courseComponentRepository.save(courseComponent); | |
| } | |
| } | |
| @Transactional | |
| public void likeCourse(String courseId) { | |
| User user = userRepository.findById(1L).orElseThrow(); | |
| ObjectMapper objectMapper = new ObjectMapper(); | |
| List<?> rawList = (List<?>) redisTemplate.opsForValue().get("tempCourse:" + courseId); | |
| List<CourseComponentDto> tempCourse = objectMapper.convertValue( | |
| rawList, | |
| new TypeReference<List<CourseComponentDto>>() {} | |
| ); | |
| tempCourse.forEach( | |
| tc -> { | |
| System.out.println(tc.tourspotId()); | |
| System.out.println(tc.tourSpotName()); | |
| } | |
| ); | |
| Course course = Course.builder() | |
| .user(user) | |
| .build(); | |
| courseRepository.save(course); | |
| for (int i = 0; i < tempCourse.size(); i++) { | |
| CourseComponentDto courseComponentDto = tempCourse.get(i); | |
| if (i == 0) { | |
| course.assignStartDtm(DateUtil.parseTime(courseComponentDto.time())); | |
| } | |
| if (i == tempCourse.size() - 1) { | |
| course.assignEndDtm(DateUtil.parseTime(courseComponentDto.time())); | |
| } | |
| TourSpot tourSpot = tourSpotRepository | |
| .findById(courseComponentDto.tourspotId()) | |
| .orElseThrow(); | |
| CourseComponent courseComponent = courseComponentMapper.toEntity( | |
| tourSpot, | |
| DateUtil.parseTime(courseComponentDto.time()), | |
| course | |
| ); | |
| courseComponentRepository.save(courseComponent); | |
| } | |
| // 트랜잭션 성공 후 Redis 데이터 삭제 | |
| redisTemplate.delete("tempCourse:" + courseId); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/com/opendata/domain/course/service/CourseService.java between
lines 209 and 245, the method likeCourse uses @Transactional but does not handle
Redis data deletion safely. To fix this, ensure that Redis data deletion happens
only after all database operations complete successfully. Move the Redis
deletion logic to after the course and course components are saved, so that if
any exception occurs, the Redis data remains intact and is only removed upon
successful transaction commit.
| // } | ||
| @Transactional | ||
| public void likeCourse(String courseId) { | ||
| User user = userRepository.findById(1L).orElseThrow(); |
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.
하드코딩된 사용자 ID 문제
사용자 ID가 1L로 하드코딩되어 있습니다. 실제 인증된 사용자 정보를 사용해야 합니다.
- User user = userRepository.findById(1L).orElseThrow();
+ // SecurityContext에서 현재 사용자 ID를 가져와야 합니다
+ Long userId = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+ User user = userRepository.findById(userId)
+ .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));🤖 Prompt for AI Agents
In src/main/java/com/opendata/domain/course/service/CourseService.java at line
211, the user ID is hardcoded as 1L when fetching the user. Replace this
hardcoded ID with the actual authenticated user's ID by retrieving it from the
security context or session, ensuring the method dynamically uses the current
user's information instead of a fixed value.
| List<?> rawList = (List<?>) redisTemplate.opsForValue().get("tempCourse:" + courseId); | ||
|
|
||
| List<CourseComponentDto> tempCourse = objectMapper.convertValue( | ||
| rawList, | ||
| new TypeReference<List<CourseComponentDto>>() {} | ||
| ); |
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.
Redis 데이터 조회 시 에러 처리 필요
Redis에서 데이터를 가져올 때 null 체크와 타입 검증이 필요합니다.
List<?> rawList = (List<?>) redisTemplate.opsForValue().get("tempCourse:" + courseId);
+ if (rawList == null || rawList.isEmpty()) {
+ throw new IllegalArgumentException("유효하지 않은 코스 ID입니다: " + courseId);
+ }
List<CourseComponentDto> tempCourse = objectMapper.convertValue(
rawList,
new TypeReference<List<CourseComponentDto>>() {}
);
+ if (tempCourse == null || tempCourse.isEmpty()) {
+ throw new IllegalStateException("코스 데이터 변환에 실패했습니다.");
+ }📝 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.
| List<?> rawList = (List<?>) redisTemplate.opsForValue().get("tempCourse:" + courseId); | |
| List<CourseComponentDto> tempCourse = objectMapper.convertValue( | |
| rawList, | |
| new TypeReference<List<CourseComponentDto>>() {} | |
| ); | |
| List<?> rawList = (List<?>) redisTemplate.opsForValue().get("tempCourse:" + courseId); | |
| if (rawList == null || rawList.isEmpty()) { | |
| throw new IllegalArgumentException("유효하지 않은 코스 ID입니다: " + courseId); | |
| } | |
| List<CourseComponentDto> tempCourse = objectMapper.convertValue( | |
| rawList, | |
| new TypeReference<List<CourseComponentDto>>() {} | |
| ); | |
| if (tempCourse == null || tempCourse.isEmpty()) { | |
| throw new IllegalStateException("코스 데이터 변환에 실패했습니다."); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/com/opendata/domain/course/service/CourseService.java around
lines 214 to 219, the code retrieves data from Redis without checking if the
result is null or verifying the type before conversion. Add a null check after
fetching the data from Redis to handle cases where the key does not exist, and
ensure the data is of the expected type before calling objectMapper.convertValue
to prevent runtime errors.
| tempCourse.forEach( | ||
| tc ->{ | ||
| System.out.println(tc.tourspotId()); | ||
| System.out.println(tc.tourSpotName()); | ||
| } | ||
| ); |
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
디버깅용 System.out.println 제거 필요
프로덕션 코드에서 System.out.println을 사용하면 안 됩니다. 적절한 로깅을 사용해야 합니다.
- tempCourse.forEach(
- tc ->{
- System.out.println(tc.tourspotId());
- System.out.println(tc.tourSpotName());
- }
- );
+ tempCourse.forEach(tc ->
+ log.debug("Processing course component - tourSpotId: {}, tourSpotName: {}",
+ tc.tourspotId(), tc.tourSpotName())
+ );📝 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.
| tempCourse.forEach( | |
| tc ->{ | |
| System.out.println(tc.tourspotId()); | |
| System.out.println(tc.tourSpotName()); | |
| } | |
| ); | |
| tempCourse.forEach(tc -> | |
| log.debug("Processing course component - tourSpotId: {}, tourSpotName: {}", | |
| tc.tourspotId(), tc.tourSpotName()) | |
| ); |
🤖 Prompt for AI Agents
In src/main/java/com/opendata/domain/course/service/CourseService.java between
lines 221 and 226, replace the System.out.println statements used for debugging
with proper logging calls using the project's logging framework. Remove the
print statements and use a logger to output the tourspotId and tourSpotName,
ensuring the log level is appropriate (e.g., debug or info) for production code.
| .authorizeHttpRequests(requests -> requests | ||
| .requestMatchers("/oauth2/**","/register/*","/login/oauth2/**", "/swagger-ui/**", // Swagger UI 관련 경로 | ||
| "/v3/api-docs/**","/api/tourspot/**", "/course","/","/login").permitAll() | ||
| "/v3/api-docs/**","/api/tourspot/**", "/course/**","/","/login").permitAll() |
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
코스 관련 엔드포인트의 보안 설정을 세분화하세요.
"/course/**" 패턴으로 변경하면 모든 코스 하위 경로에 대한 접근이 허용됩니다. 하지만 POST /course/like/{courseId}와 같은 데이터 수정 작업은 인증된 사용자만 접근할 수 있어야 합니다.
다음과 같이 보안 설정을 세분화하는 것을 권장합니다:
- .requestMatchers("/oauth2/**","/register/*","/login/oauth2/**", "/swagger-ui/**", // Swagger UI 관련 경로
- "/v3/api-docs/**","/api/tourspot/**", "/course/**","/","/login").permitAll()
+ .requestMatchers("/oauth2/**","/register/*","/login/oauth2/**", "/swagger-ui/**", // Swagger UI 관련 경로
+ "/v3/api-docs/**","/api/tourspot/**", "/course/search/**","/","/login").permitAll()
+ .requestMatchers(HttpMethod.POST, "/course/**").authenticated()📝 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.
| "/v3/api-docs/**","/api/tourspot/**", "/course/**","/","/login").permitAll() | |
| .requestMatchers("/oauth2/**","/register/*","/login/oauth2/**", "/swagger-ui/**", // Swagger UI 관련 경로 | |
| "/v3/api-docs/**","/api/tourspot/**", "/course/search/**","/","/login").permitAll() | |
| .requestMatchers(HttpMethod.POST, "/course/**").authenticated() |
🤖 Prompt for AI Agents
In src/main/java/com/opendata/global/config/SecurityConfig.java at line 95, the
current security configuration permits all access to "/course/**", which allows
unauthenticated users to perform data modification actions like POST
"/course/like/{courseId}". To fix this, refine the security rules by permitting
only safe GET requests to "/course/**" for all users, and restrict POST or other
modifying requests under "/course/**" to authenticated users. Adjust the
antMatchers or equivalent method calls to differentiate between read-only and
modifying endpoints accordingly.
|
|
||
| public class DateUtil { | ||
| private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); | ||
| private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
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.
💡 Verification agent
🧩 Analysis chain
날짜 형식 변경에 대한 기존 사용처 확인이 필요합니다.
날짜 형식을 초 단위까지 포함하도록 변경한 것은 좋습니다. 하지만 기존 코드에서 이 포맷터를 사용하는 부분이 있다면 호환성 문제가 발생할 수 있습니다.
다음 스크립트로 기존 사용처를 확인해주세요:
🏁 Script executed:
#!/bin/bash
# DateUtil의 FORMATTER 사용처 확인
rg -A 3 -B 3 "DateUtil\." --type javaLength of output: 3560
🏁 Script executed:
#!/bin/bash
sed -n '1,200p' src/main/java/com/opendata/global/util/DateUtil.javaLength of output: 1320
DateUtil 초 단위 변경 적용 시 기존 사용처 호환성 점검 및 처리 필요
초 단위까지 포함하는 포맷으로 변경되면서, 아래 사용처에서 파싱 예외나 데이터 불일치가 발생할 수 있습니다. 대응 방안(DTO 포맷 수정, 파싱 로직 확장, 쿼리 수정 등)을 검토해주세요.
-
src/main/java/com/opendata/domain/course/service/CourseService.java
• DateUtil.parseTime(courseComponentDto.time()) 호출
– courseComponentDto.time()가 기존 “yyyy-MM-dd HH:mm” 포맷일 경우 DateTimeParseException 발생 가능
▶ DTO.time() 반환값에 초(:ss) 포함 또는 parseTime에 다중 포맷 분기 추가 필요 -
src/main/java/com/opendata/domain/tourspot/service/TourSpotService.java
• currentCongestionRepository.findByTourSpotAndCurTime(..., DateUtil.getCurrentRoundedFormattedDateTime()) 호출
– 쿼리 키에 초(“:00”) 포함 여부 점검 필요
▶ DB 스키마/쿼리 매핑 업데이트 또는 초 제외 로직 도입 검토 -
선택 사항: DateUtil.parseTime 메서드에 “yyyy-MM-dd HH:mm” 포맷도 처리할 수 있는 fallback 로직 추가 고려
🤖 Prompt for AI Agents
In src/main/java/com/opendata/global/util/DateUtil.java at line 7, the
DateTimeFormatter pattern was changed to include seconds, which may cause
parsing errors in existing code expecting "yyyy-MM-dd HH:mm" format. Review all
usages, especially in CourseService.java and TourSpotService.java, and update
them accordingly. Fix this by either modifying DTO.time() to include seconds, or
enhance DateUtil.parseTime to support multiple formats with fallback logic. Also
verify and adjust any database queries or schema that rely on datetime strings
without seconds to ensure compatibility.
📌 PR 개요
✅ 변경사항
🔍 체크리스트
📎 관련 이슈
Closes #45
💬 기타 참고사항
Summary by CodeRabbit
신규 기능
버그 수정
개선 및 변경
문서화
기타