Conversation
# Conflicts: # build.gradle # postgres-data/base/1/1259 # postgres-data/base/1/2619 # postgres-data/base/1/2619_fsm # postgres-data/base/1/2619_vm # postgres-data/base/1/2696 # postgres-data/base/1/2840 # postgres-data/base/1/2841 # postgres-data/base/1/pg_internal.init # postgres-data/base/16384/1247 # postgres-data/base/16384/1249 # postgres-data/base/16384/1249_fsm # postgres-data/base/16384/1259 # postgres-data/base/16384/1259_vm # postgres-data/base/16384/2224 # postgres-data/base/16384/2579 # postgres-data/base/16384/2604 # postgres-data/base/16384/2606 # postgres-data/base/16384/2608 # postgres-data/base/16384/2610 # postgres-data/base/16384/2656 # postgres-data/base/16384/2657 # postgres-data/base/16384/2658 # postgres-data/base/16384/2659 # postgres-data/base/16384/2662 # postgres-data/base/16384/2663 # postgres-data/base/16384/2664 # postgres-data/base/16384/2665 # postgres-data/base/16384/2666 # postgres-data/base/16384/2667 # postgres-data/base/16384/2673 # postgres-data/base/16384/2674 # postgres-data/base/16384/2678 # postgres-data/base/16384/2679 # postgres-data/base/16384/2703 # postgres-data/base/16384/2704 # postgres-data/base/16384/3455 # postgres-data/base/16384/5002 # postgres-data/base/5/pg_internal.init # postgres-data/global/1260 # postgres-data/global/pg_control # postgres-data/global/pg_internal.init # postgres-data/pg_wal/000000010000000000000001 # postgres-data/pg_xact/0000 # src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java
Walkthrough과제 및 수업 자료 관리 기능을 대규모로 확장하는 변경입니다. 과제(Assignment) 관련 도메인, DTO, 예외, 리포지토리, 서비스, 컨트롤러가 신설되었고, 파일 업로드/다운로드를 위한 AWS S3 연동 및 유틸리티, 보안/환경설정, 클래스룸 및 디렉토리/문서 관리 DTO 및 서비스가 추가·확장되었습니다. 일부 기존 API와 인증, CORS, DB, 로그 설정도 변경되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Teacher as 선생님(Teacher)
participant API as AssignmentController
participant Service as AssignmentService
participant Repo as AssignmentRepository/AttachmentRepository
participant S3 as FileService/S3Uploader
Teacher->>API: POST /api/assignments/{classId} (과제 생성, 파일 포함)
API->>Service: createAssignment(userId, classId, requestDto, files)
Service->>Repo: ClassRoom, UserEntity, 학생 목록 조회
Service->>Repo: Assignment 저장
Service->>S3: 파일 저장 요청 (storeFile)
S3->>S3: S3 업로드 및 파일명 반환
Service->>Repo: AssignmentAttachment 저장
Service->>Repo: AssignmentCheck(학생별 제출상태) 생성
Service-->>API: 완료
API-->>Teacher: 201 Created
Teacher->>API: GET /api/assignments/{classId}
API->>Service: getAllAssignment(classId, userId)
Service->>Repo: Assignment, Attachment 조회
Service-->>API: Assignment 목록 반환
API-->>Teacher: Assignment 목록
Student->>API: GET /api/assignments/attachment/{attachmentId}
API->>FileService: downloadFile(attachmentId, userId)
FileService->>Repo: Attachment 메타데이터 조회
FileService->>S3: S3에서 파일 다운로드
FileService-->>API: Resource 반환
API-->>Student: 파일 다운로드
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ 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. 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.
Actionable comments posted: 51
🔭 Outside diff range comments (1)
src/main/java/hello/cluebackend/global/config/SecurityConfig.java (1)
77-77: CORS 설정과 프로덕션 환경 불일치CORS 설정이 여전히
http://localhost:3000으로 되어 있지만, 다른 설정들은 프로덕션 환경(https://clue-frontend-eight.vercel.app)으로 변경되었습니다. 프로덕션 배포 시 CORS 설정도 함께 업데이트해야 합니다.- configuration.setAllowedOriginPatterns(Collections.singletonList("http://localhost:3000")); + configuration.setAllowedOriginPatterns(Collections.singletonList("https://clue-frontend-eight.vercel.app"));
🧹 Nitpick comments (24)
src/main/java/hello/cluebackend/domain/timetable/service/TimetableService.java (1)
78-78: 불필요한 이중 공백 제거 권장
메서드 시그니처의>>뒤에 공백이 두 칸 들어가 있습니다. 팀 코딩 컨벤션이나 Checkstyle 규칙에 따라 포맷팅 오류로 간주될 수 있으니 한 칸으로 정리해 주세요.src/main/java/hello/cluebackend/domain/assignment/domain/SubmitType.java (1)
4-6: 문서화 개선을 고려해보세요.각 제출 유형에 대한 간단한 JavaDoc 주석을 추가하면 향후 유지보수와 가독성에 도움이 될 것입니다.
public enum SubmitType { + /** 파일 업로드 제출 */ FILE, + /** 링크 URL 제출 */ LINK, + /** 이미지 파일 제출 */ IMAGE }src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java (1)
42-42: 로그 메시지 형식을 개선하세요.문자열 연결 대신 SLF4J의 매개변수화된 메시지를 사용하는 것이 성능과 가독성 면에서 더 좋습니다.
- log.info("ClassCode 1 : " + defaultRegisterUserDTO.getClassCode()); + log.info("ClassCode 1 : {}", defaultRegisterUserDTO.getClassCode());src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java (1)
40-44: 주석 처리된 코드 정리 필요주석 처리된 개발 환경 URL들을 제거하는 것을 고려해보세요. 버전 관리 시스템에서 변경 이력을 추적할 수 있으므로 코드의 가독성을 위해 불필요한 주석을 제거하는 것이 좋습니다.
-// getRedirectStrategy().sendRedirect( -// request, -// response, -// "http://localhost:3000/register" -// ); getRedirectStrategy().sendRedirect( request, response, "https://clue-frontend-eight.vercel.app/register" );Also applies to: 66-66
src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/Assignmentfile.java (1)
3-7: 레코드 설계 승인 및 데이터 타입 검토Java record를 사용한 불변 DTO 설계가 적절합니다. 간결하고 명확한 구조입니다.
다만
fileSize를int타입으로 사용한 것에 대해 검토가 필요합니다. 큰 파일의 경우int의 최대값(약 2GB)을 초과할 수 있습니다.큰 파일 지원을 위해
long타입 사용을 고려해보세요:public record Assignmentfile( Long fileId, String fileName, - int fileSize + long fileSize ) {}src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentDuration.java (1)
5-8: 과제 기간 레코드 설계 승인
LocalDateTime을 사용한 과제 기간 표현이 적절합니다. 불변 레코드 구조로 깔끔하게 설계되었습니다.비즈니스 로직에서
startDate가endDate보다 이전인지 검증하는 것을 고려해보세요. DTO 자체에는 검증 로직을 넣지 않는 것이 맞지만, 서비스 레이어에서는 이런 검증이 필요할 수 있습니다.src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/StudentAssignmentRemain.java (1)
5-5: duration 필드의 의미를 명확히 하는 것을 고려해보세요.duration 필드가 String 타입으로 선언되어 있는데, 이것이 남은 시간인지 전체 기간인지, 어떤 형식(예: "3일 남음", "2025-07-15")인지 명확하지 않습니다. 필드명을 더 구체적으로 변경하거나 JavaDoc 주석을 추가하는 것을 권장합니다.
public record StudentAssignmentRemain( String title, - String duration, + String remainingDuration, // 또는 daysRemaining Long assignmentId ) {}build.gradle (1)
35-35: 중복된 validation 의존성을 제거하세요.34번 라인과 35번 라인에 동일한
spring-boot-starter-validation의존성이 중복으로 선언되어 있습니다.implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-validation'src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAssignmentResponseDto.java (1)
10-10: 필드명 일관성을 개선하세요.
endDate는LocalDateTime타입이고duringDate는String타입입니다. 의미가 명확하도록duringDate대신remainingDuration또는durationText같은 더 명확한 이름을 사용하는 것을 고려해보세요.- String duringDate, + String remainingDuration,src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentCreateRequestDto.java (1)
8-14: 검증 어노테이션 추가를 고려해보세요.과제 생성 요청에 대한 입력 검증을 위해 Bean Validation 어노테이션 추가를 고려해보세요.
+import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; @Getter @Setter public class AssignmentCreateRequestDto { + @NotBlank(message = "제목은 필수입니다") private String title; + @NotBlank(message = "내용은 필수입니다") private String content; + @NotNull(message = "시작일은 필수입니다") private LocalDateTime startDate; + @NotNull(message = "마감일은 필수입니다") private LocalDateTime dueDate; }src/main/resources/application.yaml (1)
53-64: AWS 설정 개선을 고려해보세요.현재 AWS 설정에서 몇 가지 개선점이 있습니다:
- 리전 외부화: 리전도 환경 변수로 설정하면 더 유연합니다.
- 설정 검증: 필수 환경 변수들에 대한 기본값이나 검증이 필요할 수 있습니다.
cloud: aws: credentials: accessKey: ${AWS_ACCESS_KEY} secretKey: ${AWS_SECRET_KEY} s3: bucket: ${AWS_BUCKET_NAME} region: - static: ap-northeast-2 + static: ${AWS_REGION:ap-northeast-2} stack: auto: falsesrc/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java (2)
37-38: 파일 크기 데이터 타입 개선 제안
Integer타입은 최대 약 2GB까지만 표현 가능하여 대용량 파일 처리에 제한이 있습니다.
Long타입 사용을 권장합니다:@Column(name = "file_size") - private Integer fileSize; + private Long fileSize;
28-45: 파일 관련 필드에 검증 어노테이션 추가를 권장합니다.파일명과 경로에 대한 기본 검증을 추가하면 데이터 무결성을 보장할 수 있습니다.
+ import jakarta.validation.constraints.NotBlank; + import jakarta.validation.constraints.Size; @Column(name = "original_file_name") + @NotBlank(message = "원본 파일명은 필수입니다") + @Size(max = 255, message = "파일명은 255자를 초과할 수 없습니다") private String originalFileName; @Column(name = "stored_file_name") + @NotBlank(message = "저장된 파일명은 필수입니다") + @Size(max = 255, message = "파일명은 255자를 초과할 수 없습니다") private String storedFileName; @Column(name = "file_path") + @NotBlank(message = "파일 경로는 필수입니다") + @Size(max = 500, message = "파일 경로는 500자를 초과할 수 없습니다") private String filePath;src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentCheckRepository.java (2)
14-23: 쿼리 성능 최적화 고려사항현재 쿼리는
ClassRoomUserList와의 조인을 포함하고 있지만 WHERE 절에서 사용되지 않습니다. 또한 대량의 결과에 대한 페이징 처리가 없습니다.불필요한 조인 제거와 페이징 지원을 고려해보세요:
@Query(""" SELECT ac.assignment FROM AssignmentCheck ac JOIN ac.assignment a JOIN a.classRoom cr WHERE ac.user.userId = :userId AND ac.isSubmitted = false """) Page<Assignment> findUnsubmittedAssignmentsByUserId(@Param("userId") Long userId, Pageable pageable);
14-23: JPQL 쿼리 최적화 및 문서화 권장쿼리가 복잡하므로 성능 최적화와 가독성을 위한 개선을 권장합니다.
쿼리에 주석을 추가하고 인덱스 힌트를 고려해보세요:
@Query(""" + -- 특정 사용자의 미제출 과제 조회 + -- AssignmentCheck 테이블을 기준으로 제출하지 않은 과제들을 찾음 SELECT ac.assignment FROM AssignmentCheck ac JOIN ac.assignment a JOIN a.classRoom cr JOIN cr.classRoomUserList cru WHERE ac.user.userId = :userId AND ac.isSubmitted = false """)또한
user_id와is_submitted필드에 복합 인덱스 생성을 고려해보세요.src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java (2)
3-3: 사용하지 않는 import 제거
AssignmentAttachmentDto를 import하고 있지만 이 클래스에서 사용되지 않습니다.다음 import를 제거하세요:
-import hello.cluebackend.domain.assignment.presentation.dto.AssignmentAttachmentDto;
3-3: 사용하지 않는 import 제거
AssignmentAttachmentDtoimport가 사용되지 않습니다.- import hello.cluebackend.domain.assignment.presentation.dto.AssignmentAttachmentDto;src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentContent.java (1)
37-38: 파일 크기 제한을 고려하여 데이터 타입을 변경하세요.
int타입은 약 2.1GB까지만 표현 가능합니다. 더 큰 파일을 지원하려면long타입을 사용하세요.- private int fileSize; + private long fileSize;src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java (1)
42-42: 주석 처리된 코드를 제거하거나 명확한 설명을 추가하세요.역할 필터가 주석 처리되어 있습니다. 의도적인 변경이라면 주석을 제거하고, 필요시 코드 설명을 추가하세요.
-// .filter(cu -> cu.getUser().getRole() == Role.STUDENT)src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java (4)
3-3: 사용하지 않는 import 제거 필요
HikariProxyCallableStatement와AssignmentContentRepository는 코드에서 사용되지 않으므로 제거해주세요.-import com.zaxxer.hikari.pool.HikariProxyCallableStatement;-import hello.cluebackend.domain.assignment.domain.repository.AssignmentContentRepository;Also applies to: 9-9
64-64: 디버그 코드 제거주석 처리된 디버그 출력문은 제거해주세요.
-// System.out.println("user = " + user);
170-205: 주석 처리된 코드 제거사용하지 않는 주석 코드는 제거하여 코드 가독성을 높이세요.
275-347: 주석 처리된 코드 블록 제거파일 끝부분의 주석 처리된 메서드들은 제거해주세요. 버전 관리 시스템에서 필요시 확인 가능합니다.
src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java (1)
95-115: 주석 처리된 코드 제거사용하지 않는 주석 코드를 제거하여 코드 가독성을 개선하세요.
Also applies to: 129-168
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
.DS_Storeis excluded by!**/.DS_Storesrc/main/.DS_Storeis excluded by!**/.DS_Storesrc/main/resources/.DS_Storeis excluded by!**/.DS_Store
📒 Files selected for processing (43)
build.gradle(2 hunks)src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentContent.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/domain/SubmitType.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentAttachmentRepository.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentCheckRepository.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentContentRepository.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentRepository.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/exception/AssignmentNotFoundException.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/exception/UnauthorizedException.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/presentation/dto/AssignmentAttachmentDto.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentCreateRequestDto.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentSubmitStatusDto.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentDuration.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/Assignmentfile.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAssignmentResponseDto.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/StudentAssignmentRemain.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java(1 hunks)src/main/java/hello/cluebackend/domain/assignment/service/FileService.java(1 hunks)src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java(4 hunks)src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java(1 hunks)src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java(4 hunks)src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java(2 hunks)src/main/java/hello/cluebackend/domain/directory/domain/Directory.java(2 hunks)src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java(1 hunks)src/main/java/hello/cluebackend/domain/document/domain/Document.java(2 hunks)src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java(2 hunks)src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java(1 hunks)src/main/java/hello/cluebackend/domain/document/service/DocumentService.java(2 hunks)src/main/java/hello/cluebackend/domain/timetable/service/TimetableService.java(1 hunks)src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java(2 hunks)src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java(1 hunks)src/main/java/hello/cluebackend/domain/user/presentation/TestController.java(1 hunks)src/main/java/hello/cluebackend/global/config/CorsMvcConfig.java(1 hunks)src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java(2 hunks)src/main/java/hello/cluebackend/global/config/FileUtil.java(1 hunks)src/main/java/hello/cluebackend/global/config/S3Config.java(1 hunks)src/main/java/hello/cluebackend/global/config/S3Uploader.java(1 hunks)src/main/java/hello/cluebackend/global/config/SecurityConfig.java(1 hunks)src/main/resources/application.yaml(2 hunks)
🧰 Additional context used
🧠 Learnings (13)
src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java (2)
Learnt from: fixgramwork
PR: #23
File: src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java:17-23
Timestamp: 2025-07-08T23:38:29.680Z
Learning: This project uses OAuth2 authentication and does not require traditional password storage or validation. The RegisterUserService in src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java should not store passwords since OAuth2 handles authentication externally.
Learnt from: Hgyeol
PR: #36
File: src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java:34-39
Timestamp: 2025-07-13T16:08:21.631Z
Learning: In this Spring Boot application, JWT validation is handled at the filter level before requests reach the controllers, so JWT token extraction and user ID retrieval in controllers like ClassRoomController don't require additional exception handling for invalid or missing tokens.
src/main/java/hello/cluebackend/domain/user/presentation/TestController.java (1)
Learnt from: Hgyeol
PR: #36
File: src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java:34-39
Timestamp: 2025-07-13T16:08:21.631Z
Learning: In this Spring Boot application, JWT validation is handled at the filter level before requests reach the controllers, so JWT token extraction and user ID retrieval in controllers like ClassRoomController don't require additional exception handling for invalid or missing tokens.
src/main/java/hello/cluebackend/global/config/SecurityConfig.java (2)
Learnt from: fixgramwork
PR: #23
File: src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java:17-23
Timestamp: 2025-07-08T23:38:29.680Z
Learning: This project uses OAuth2 authentication and does not require traditional password storage or validation. The RegisterUserService in src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java should not store passwords since OAuth2 handles authentication externally.
Learnt from: Hgyeol
PR: #36
File: src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java:34-39
Timestamp: 2025-07-13T16:08:21.631Z
Learning: In this Spring Boot application, JWT validation is handled at the filter level before requests reach the controllers, so JWT token extraction and user ID retrieval in controllers like ClassRoomController don't require additional exception handling for invalid or missing tokens.
build.gradle (1)
Learnt from: Hgyeol
PR: #36
File: src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java:34-39
Timestamp: 2025-07-13T16:08:21.631Z
Learning: In this Spring Boot application, JWT validation is handled at the filter level before requests reach the controllers, so JWT token extraction and user ID retrieval in controllers like ClassRoomController don't require additional exception handling for invalid or missing tokens.
src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java (1)
Learnt from: fixgramwork
PR: #23
File: src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java:17-23
Timestamp: 2025-07-08T23:38:29.680Z
Learning: This project uses OAuth2 authentication and does not require traditional password storage or validation. The RegisterUserService in src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java should not store passwords since OAuth2 handles authentication externally.
src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java (2)
Learnt from: fixgramwork
PR: #23
File: src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java:17-23
Timestamp: 2025-07-08T23:38:29.680Z
Learning: This project uses OAuth2 authentication and does not require traditional password storage or validation. The RegisterUserService in src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java should not store passwords since OAuth2 handles authentication externally.
Learnt from: Hgyeol
PR: #36
File: src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java:34-39
Timestamp: 2025-07-13T16:08:21.631Z
Learning: In this Spring Boot application, JWT validation is handled at the filter level before requests reach the controllers, so JWT token extraction and user ID retrieval in controllers like ClassRoomController don't require additional exception handling for invalid or missing tokens.
src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java (2)
Learnt from: Hgyeol
PR: #36
File: src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java:34-39
Timestamp: 2025-07-13T16:08:21.631Z
Learning: In this Spring Boot application, JWT validation is handled at the filter level before requests reach the controllers, so JWT token extraction and user ID retrieval in controllers like ClassRoomController don't require additional exception handling for invalid or missing tokens.
Learnt from: fixgramwork
PR: #23
File: src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java:17-23
Timestamp: 2025-07-08T23:38:29.680Z
Learning: This project uses OAuth2 authentication and does not require traditional password storage or validation. The RegisterUserService in src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java should not store passwords since OAuth2 handles authentication externally.
src/main/java/hello/cluebackend/domain/assignment/service/FileService.java (1)
Learnt from: fixgramwork
PR: #23
File: src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java:17-23
Timestamp: 2025-07-08T23:38:29.680Z
Learning: This project uses OAuth2 authentication and does not require traditional password storage or validation. The RegisterUserService in src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java should not store passwords since OAuth2 handles authentication externally.
src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java (2)
Learnt from: Hgyeol
PR: #36
File: src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java:34-39
Timestamp: 2025-07-13T16:08:21.631Z
Learning: In this Spring Boot application, JWT validation is handled at the filter level before requests reach the controllers, so JWT token extraction and user ID retrieval in controllers like ClassRoomController don't require additional exception handling for invalid or missing tokens.
Learnt from: fixgramwork
PR: #23
File: src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java:17-23
Timestamp: 2025-07-08T23:38:29.680Z
Learning: This project uses OAuth2 authentication and does not require traditional password storage or validation. The RegisterUserService in src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java should not store passwords since OAuth2 handles authentication externally.
src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java (1)
Learnt from: fixgramwork
PR: #23
File: src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java:17-23
Timestamp: 2025-07-08T23:38:29.680Z
Learning: This project uses OAuth2 authentication and does not require traditional password storage or validation. The RegisterUserService in src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java should not store passwords since OAuth2 handles authentication externally.
src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java (1)
Learnt from: Hgyeol
PR: #36
File: src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java:34-39
Timestamp: 2025-07-13T16:08:21.631Z
Learning: In this Spring Boot application, JWT validation is handled at the filter level before requests reach the controllers, so JWT token extraction and user ID retrieval in controllers like ClassRoomController don't require additional exception handling for invalid or missing tokens.
src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java (1)
Learnt from: Hgyeol
PR: #36
File: src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java:34-39
Timestamp: 2025-07-13T16:08:21.631Z
Learning: In this Spring Boot application, JWT validation is handled at the filter level before requests reach the controllers, so JWT token extraction and user ID retrieval in controllers like ClassRoomController don't require additional exception handling for invalid or missing tokens.
src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java (1)
Learnt from: Hgyeol
PR: #36
File: src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java:34-39
Timestamp: 2025-07-13T16:08:21.631Z
Learning: In this Spring Boot application, JWT validation is handled at the filter level before requests reach the controllers, so JWT token extraction and user ID retrieval in controllers like ClassRoomController don't require additional exception handling for invalid or missing tokens.
🧬 Code Graph Analysis (16)
src/main/java/hello/cluebackend/domain/assignment/domain/SubmitType.java (1)
src/main/java/hello/cluebackend/domain/user/domain/Role.java (1)
Role(3-7)
src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java (1)
src/main/java/hello/cluebackend/domain/user/presentation/dto/DefaultRegisterUserDto.java (1)
DefaultRegisterUserDto(7-14)
src/main/java/hello/cluebackend/domain/assignment/exception/AssignmentNotFoundException.java (1)
src/main/java/hello/cluebackend/domain/assignment/exception/UnauthorizedException.java (1)
ResponseStatus(6-15)
src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentAttachmentRepository.java (5)
src/main/java/hello/cluebackend/domain/document/domain/repository/DocumentRepository.java (1)
DocumentRepository(7-9)src/main/java/hello/cluebackend/domain/directory/domain/repository/DirectoryRepository.java (1)
DirectoryRepository(7-9)src/main/java/hello/cluebackend/domain/classroom/domain/repository/ClassRoomRepository.java (1)
ClassRoomRepository(10-15)src/main/java/hello/cluebackend/domain/user/domain/repository/UserRepository.java (1)
UserRepository(9-14)src/main/java/hello/cluebackend/global/security/jwt/RefreshTokenRepository.java (1)
RefreshTokenRepository(6-9)
src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/Assignmentfile.java (1)
src/main/java/hello/cluebackend/domain/document/presentation/dto/FileUpload.java (1)
FileUpload(8-20)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java (3)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (1)
Getter(8-18)src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (2)
DocumentDto(13-37)toEntity(26-36)src/main/java/hello/cluebackend/domain/document/presentation/dto/RequestDocumentDto.java (1)
RequestDocumentDto(5-17)
src/main/java/hello/cluebackend/global/config/S3Uploader.java (3)
src/main/java/hello/cluebackend/domain/assignment/service/FileService.java (1)
Service(19-60)src/main/java/hello/cluebackend/domain/document/service/LocalStorageService.java (2)
LocalStorageService(15-72)upload(26-52)src/main/java/hello/cluebackend/domain/document/presentation/dto/FileUpload.java (1)
FileUpload(8-20)
src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java (2)
src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java (1)
Entity(11-41)src/main/java/hello/cluebackend/domain/assignment/presentation/dto/AssignmentAttachmentDto.java (1)
Getter(10-26)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java (5)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (1)
Getter(8-18)src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java (1)
Getter(7-16)src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomDto.java (2)
ClassRoomDto(12-51)toEntity(39-50)src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomCardDto.java (1)
ClassRoomCardDto(5-17)src/main/java/hello/cluebackend/domain/classroom/domain/ClassRoom.java (2)
toDTO(63-74)ClassRoom(14-86)
src/main/java/hello/cluebackend/global/config/FileUtil.java (2)
src/main/java/hello/cluebackend/domain/document/service/LocalStorageService.java (2)
generateStoredFileName(68-70)LocalStorageService(15-72)src/main/java/hello/cluebackend/domain/document/presentation/dto/FileUpload.java (1)
FileUpload(8-20)
src/main/java/hello/cluebackend/global/config/S3Config.java (4)
src/main/java/hello/cluebackend/ClueBackendApplication.java (1)
ClueBackendApplication(7-14)src/main/java/hello/cluebackend/global/config/WebClientConfig.java (1)
WebClientConfig(17-46)src/main/java/hello/cluebackend/global/config/WebConfig.java (1)
WebConfig(10-23)src/main/java/hello/cluebackend/global/config/RedisConfig.java (1)
RedisConfig(14-36)
src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java (1)
src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java (1)
Entity(11-41)
src/main/java/hello/cluebackend/domain/assignment/service/FileService.java (3)
src/main/java/hello/cluebackend/global/config/S3Uploader.java (1)
Service(14-48)src/main/java/hello/cluebackend/domain/document/service/LocalStorageService.java (3)
LocalStorageService(15-72)upload(26-52)storeFiles(54-65)src/main/java/hello/cluebackend/domain/document/presentation/dto/FileUpload.java (1)
FileUpload(8-20)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (5)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java (1)
Getter(8-20)src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java (1)
Getter(7-16)src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (2)
DirectoryDto(10-29)toDto(21-28)src/main/java/hello/cluebackend/domain/directory/presentation/dto/RequestDirectoryDto.java (1)
RequestDirectoryDto(5-11)src/main/java/hello/cluebackend/domain/directory/service/DirectoryService.java (1)
DirectoryService(10-49)
src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java (4)
src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java (1)
Slf4j(27-168)src/main/java/hello/cluebackend/domain/assignment/service/FileService.java (1)
Service(19-60)src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java (1)
Service(27-126)src/main/java/hello/cluebackend/domain/user/service/UserService.java (1)
UserService(9-30)
src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java (1)
src/main/java/hello/cluebackend/domain/directory/presentation/DirectoryController.java (1)
updateDirectory(43-56)
🪛 YAMLlint (1.37.1)
src/main/resources/application.yaml
[warning] 59-59: too many spaces before colon
(colons)
🔇 Additional comments (26)
src/main/java/hello/cluebackend/domain/assignment/domain/SubmitType.java (1)
3-7: 깔끔한 구현입니다!제출 유형을 명확하게 구분하는 간단하고 직관적인 enum입니다. 기존
Roleenum과 일관된 스타일을 따르고 있어 좋습니다.src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java (1)
42-42: 로그 레벨 변경이 적절합니다.디버그에서 정보 레벨로 변경하여 클래스 코드를 더 명확하게 추적할 수 있게 되었습니다.
src/main/java/hello/cluebackend/global/config/CorsMvcConfig.java (1)
14-14: 네트워크 주소 검증이 필요합니다.
10.129.57.136IP 주소가 의도된 개발 환경인지 확인해주세요. 이 주소가 올바른 프론트엔드 서버를 가리키는지 검증이 필요합니다.src/main/java/hello/cluebackend/global/config/SecurityConfig.java (1)
117-119: 문서 API 보안 강화 의도 확인 필요SecurityConfig.java에서
/api/document/download/{documentId:\d+}가 permitAll 목록에서 제거되어, DocumentController의 모든 엔드포인트가 인증을 요구합니다. 다음 사항을 검토해주세요:• 대상 파일
- src/main/java/hello/cluebackend/global/config/SecurityConfig.java (라인 117–119)
• 영향을 받는 컨트롤러 및 엔드포인트- src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java
- 클래스 레벨:
@RequestMapping("/api/document")- POST
/api/document(등록)- PATCH
/api/document(수정)- DELETE
/api/document(삭제)- GET
/api/document/download/{documentId}(다운로드)- POST
/api/document/test(테스트)특히 다운로드 기능이 인증된 사용자만 접근해야 하는지, 혹은 익명 접근을 허용해야 하는지 확인 후, 의도와 다를 경우 SecurityConfig에 해당 경로를 permitAll에 다시 추가해주세요.
src/main/java/hello/cluebackend/domain/document/domain/Document.java (1)
7-7: Lombok 와일드카드 임포트 사용 승인Lombok 어노테이션들을 와일드카드 임포트로 통합한 것은 적절합니다. 코드가 더 간결해졌습니다.
src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/StudentAssignmentRemain.java (1)
3-7: 레코드 구조가 적절하게 설계되었습니다.Java 레코드를 사용한 불변 데이터 캐리어 패턴이 올바르게 적용되었습니다. 응답 DTO로서의 역할에 적합한 구조를 가지고 있습니다.
src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentRepository.java (1)
8-10: JPA 리포지토리 구현이 올바릅니다.Spring Data JPA 규칙을 따른 메서드 명명과 속성 경로 탐색(
ClassRoom_ClassRoomId)이 정확하게 구현되었습니다. 클래스룸별 과제 조회 기능이 적절하게 설계되었습니다.src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentContentRepository.java (1)
6-7: 기본 CRUD 리포지토리 구현이 적절합니다.현재 단계에서는 기본 CRUD 작업만 필요하므로, 커스텀 메서드 없이 JpaRepository를 상속하는 깔끔한 설계입니다. 향후 필요에 따라 커스텀 쿼리 메서드를 추가할 수 있는 확장 가능한 구조입니다.
build.gradle (1)
36-36: Actuator 의존성 추가가 적절합니다.애플리케이션 모니터링을 위한 Spring Boot Actuator 추가는 운영 환경에서 유용합니다.
src/main/java/hello/cluebackend/domain/directory/domain/Directory.java (2)
33-34: 디렉토리-문서 관계 설정이 적절합니다.양방향 @onetomany 관계가 올바르게 구현되었고, 기본 LAZY 페치 타입이 성능상 적절합니다.
33-33: CascadeType.ALL의 적절성을 검토하세요.
CascadeType.ALL은 디렉토리 삭제 시 모든 문서가 함께 삭제됨을 의미합니다. 비즈니스 요구사항에 따라 이것이 적절한지 확인하고, 필요하다면 더 구체적인 cascade 타입(CascadeType.PERSIST,CascadeType.MERGE등)을 사용하는 것을 고려해보세요.src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentSubmitStatusDto.java (1)
8-8:submittedAt필드의 null 처리 검증을 추가하세요.
isSubmitted가 false일 때submittedAt이 null일 수 있는 상황을 명시적으로 처리해야 합니다. 검증 로직이나 문서화를 추가하는 것을 권장합니다.src/main/java/hello/cluebackend/domain/assignment/exception/UnauthorizedException.java (1)
6-15: 잘 구현된 사용자 정의 예외 클래스입니다.
@ResponseStatus어노테이션을 적절히 사용하여 HTTP 403 상태를 반환하고, 기본 메시지와 사용자 정의 메시지를 모두 지원하는 생성자를 제공합니다. Spring Boot 모범 사례를 잘 따르고 있습니다.src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentAttachmentRepository.java (1)
10-13: 잘 구현된 리포지토리 인터페이스입니다.Spring Data JPA 규칙을 잘 따르고 있으며,
findAllByAssignment메서드명도 Spring Data의 쿼리 메서드 네이밍 규칙에 적합합니다. 다른 리포지토리들과 일관된 패턴을 유지하고 있습니다.src/main/java/hello/cluebackend/domain/assignment/exception/AssignmentNotFoundException.java (1)
6-15: 일관성 있고 잘 구현된 예외 클래스입니다.
UnauthorizedException과 동일한 패턴을 따라 일관성을 유지하고 있으며, HTTP 404 상태 코드를 적절히 사용합니다. 기본 메시지와 사용자 정의 메시지를 모두 지원하는 구조가 좋습니다.src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java (2)
13-13: LGTM!
@ToString어노테이션 추가로 디버깅 시 객체 상태 확인이 용이해집니다.
51-52: LGTM!
@Builder.Default어노테이션 추가로 Builder 패턴 사용 시classRoomUserList가 올바르게 초기화됩니다. 이는 NPE 방지에 도움이 됩니다.src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java (1)
7-16: LGTM!DTO 클래스가 잘 구성되어 있습니다. 다른 DTO 클래스들과 일관된 Lombok 어노테이션 사용 패턴을 따르고 있으며, 문서 정보 전송에 필요한 핵심 필드들을 적절히 포함하고 있습니다.
src/main/resources/application.yaml (1)
5-8: LGTM!데이터베이스 설정을 환경 변수로 외부화한 것은 보안 모범 사례를 따른 좋은 변경입니다.
src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java (1)
59-59: 예외 처리 개선이 잘 되었습니다.일반적인
RuntimeException대신 구체적인EntityNotFoundException을 catch하도록 변경한 것은 더 정확한 예외 처리를 위한 좋은 개선입니다.src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java (1)
8-20: 잘 설계된 DTO 구조입니다.수업 정보를 포괄적으로 담는 DTO 구조가 적절하며, 중첩된 DTO를 사용한 것도 좋은 접근 방식입니다. Lombok 어노테이션 사용도 적절합니다.
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (1)
8-18: 일관성 있고 잘 구조화된 DTO입니다.디렉토리와 관련 문서들을 계층적으로 표현하는 구조가 적절하며,
DocumentAllInfoDto를 중첩하여 사용한 것도 좋은 설계입니다. 다른 DTO들과 일관된 네이밍 컨벤션을 따르고 있습니다.src/main/java/hello/cluebackend/domain/assignment/service/FileService.java (1)
31-33: null 체크를 추가하여 NullPointerException을 방지하세요.
originalFilename이 null인 경우substring호출 시 NPE가 발생합니다.- if (originalFilename != null && originalFilename.contains(".")) { - extension = originalFilename.substring(originalFilename.lastIndexOf(".")); - } + if (originalFilename != null) { + int lastDotIndex = originalFilename.lastIndexOf("."); + if (lastDotIndex > 0) { + extension = originalFilename.substring(lastDotIndex); + } + }Likely an incorrect or invalid review comment.
src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java (1)
54-54: 더 구체적인 예외 타입 사용이 좋습니다.
RuntimeException대신EntityNotFoundException을 사용한 것은 좋은 개선입니다.src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java (1)
65-87: PATCH 엔드포인트 구현이 적절합니다.기존 POST 엔드포인트와 일관된 패턴을 따르고 있으며, 교사 권한 검증도 올바르게 구현되었습니다.
src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java (1)
151-168: 파일 저장 전략 일관성 필요현재 S3를 지원하는
FileService.storeFile()대신 로컬 업로드를 사용하고 있습니다. 파일 저장 전략을 통일해주세요.S3와 로컬 저장소 중 어떤 전략을 사용할지 결정이 필요합니다. 현재는 로컬 저장소를 사용하지만, FileService는 S3를 사용하도록 구현되어 있습니다.
| implementation 'software.amazon.awssdk:s3:2.32.1' | ||
| implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Spring Cloud AWS 버전을 업데이트해야 합니다.
AWS S3 SDK는 최신 버전(2.32.1)을 사용하고 있지만, Spring Cloud AWS 버전(2.2.6.RELEASE)이 너무 오래되었습니다. 이는 Spring Boot 3.x와 호환성 문제를 일으킬 수 있습니다.
다음과 같이 최신 버전으로 업데이트하는 것을 권장합니다:
- implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
+ implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.1.1'Spring Cloud AWS의 최신 버전 확인을 위해 다음을 검색해주세요:
🌐 Web query:
What is the latest version of Spring Cloud AWS that is compatible with Spring Boot 3.x?
💡 Result:
The latest version of Spring Cloud AWS compatible with Spring Boot 3.x is 3.4.0, which works with Spring Boot 3.5.x, while 3.3.x supports Spring Boot 3.4.x, 3.2.x supports 3.2.x–3.3.x, and 3.0.x supports 3.0.x–3.1.x[1][4][6][7].
Compatibility Table
| Spring Cloud AWS | Spring Boot Compatibility | Spring Framework | AWS Java SDK |
|---|---|---|---|
| 3.0.x | 3.0.x, 3.1.x | 6.0.x | 2.x |
| 3.1.x | 3.2.x | 6.1.x | 2.x |
| 3.2.x | 3.2.x, 3.3.x | 6.1.x | 2.x |
| 3.3.x | 3.4.x | 6.2.x | 2.x |
| 3.4.x | 3.5.x | 6.2.x | 2.x |
- Spring Cloud AWS 3.4.0 is the release for Spring Boot 3.5.x[1].
- Older 3.x versions are for earlier 3.x releases of Spring Boot: for example, 3.2.x is intended for 3.2.x–3.3.x[4][6][7].
Additional Details
- The Spring Cloud AWS project migrated to supporting Spring Boot 3.x starting with 3.0.0-M3 (milestone 3)[1][4].
- Each major/minor version in the 3.x series is paired with the corresponding Spring Boot version (e.g., 3.3.x with Spring Boot 3.4.x)[1].
- To use the most recent Spring Boot 3.x (i.e., 3.5.x), select Spring Cloud AWS 3.4.0[1].
Reference Links (For Internal Team)
- [Spring Cloud AWS GitHub Releases][6]
- [Spring Cloud AWS Compatibility Table][1]
If you are targeting a specific 3.x release of Spring Boot, match the Spring Cloud AWS version accordingly using the table above to ensure full compatibility[1][4][7].
Citations:
- 1: https://github.com/awspring/spring-cloud-aws
- 2: Spring Boot 3 compatibility awspring/spring-cloud-aws#780
- 3: Timeline for Spring Boot 3.3.x compatible version? awspring/spring-cloud-aws#1159
- 4: https://awspring.io/what-is-spring-cloud-aws
- 5: https://spring.io/blog/2024/05/31/spring-cloud-2023-0-2-oss-and-2022-0-7-enterprise-have-been-released/
- 6: https://github.com/awspring/spring-cloud-aws/releases
- 7: https://cloudsmith.com/navigator/maven/io.awspring.cloud:spring-cloud-aws-sqs
- 8: https://trifork.nl/blog/upgrading-to-spring-cloud-aws-3/
- 9: https://spring.io/projects/spring-cloud
- 10: https://spring.io/blog/2023/05/02/announcing-spring-cloud-aws-3-0-0
Spring Cloud AWS 버전을 Spring Boot 3.x 호환 최신(3.4.0)으로 업그레이드하세요
다음 위치를 수정해 주세요:
- build.gradle (69–70줄)
- implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
+ implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.0'Spring Boot 3.5.x 사용 시 Spring Cloud AWS 3.4.0이 호환되므로 해당 버전으로 변경이 필요합니다.
📝 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.
| implementation 'software.amazon.awssdk:s3:2.32.1' | |
| implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' | |
| implementation 'software.amazon.awssdk:s3:2.32.1' | |
| implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.0' |
🤖 Prompt for AI Agents
In build.gradle at lines 69 to 70, update the Spring Cloud AWS dependency
version from 2.2.6.RELEASE to 3.4.0 to ensure compatibility with Spring Boot
3.x. Replace the existing version string with '3.4.0' for the
'org.springframework.cloud:spring-cloud-starter-aws' dependency.
| @Column(name = "title") | ||
| private String title; | ||
|
|
||
| @Column(name = "content") | ||
| private String content; | ||
|
|
||
| @Column(name = "start_date") | ||
| private LocalDateTime startDate; | ||
|
|
||
| @Column(name = "due_date") | ||
| private LocalDateTime dueDate; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
필수 필드에 대한 검증 어노테이션 추가 권장
제목이나 내용 같은 필수 필드에 대한 검증이 없어 데이터 무결성 문제가 발생할 수 있습니다.
Bean Validation 어노테이션을 추가하세요:
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
@Column(name = "title")
+ @NotBlank(message = "제목은 필수입니다")
private String title;
@Column(name = "content")
+ @NotBlank(message = "내용은 필수입니다")
private String content;
@Column(name = "start_date")
+ @NotNull(message = "시작일은 필수입니다")
private LocalDateTime startDate;
@Column(name = "due_date")
+ @NotNull(message = "마감일은 필수입니다")
private LocalDateTime dueDate;📝 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.
| @Column(name = "title") | |
| private String title; | |
| @Column(name = "content") | |
| private String content; | |
| @Column(name = "start_date") | |
| private LocalDateTime startDate; | |
| @Column(name = "due_date") | |
| private LocalDateTime dueDate; | |
| import jakarta.validation.constraints.NotBlank; | |
| import jakarta.validation.constraints.NotNull; | |
| @Column(name = "title") | |
| @NotBlank(message = "제목은 필수입니다") | |
| private String title; | |
| @Column(name = "content") | |
| @NotBlank(message = "내용은 필수입니다") | |
| private String content; | |
| @Column(name = "start_date") | |
| @NotNull(message = "시작일은 필수입니다") | |
| private LocalDateTime startDate; | |
| @Column(name = "due_date") | |
| @NotNull(message = "마감일은 필수입니다") | |
| private LocalDateTime dueDate; |
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java
around lines 30 to 40, the fields title and content lack validation annotations,
risking data integrity issues. Add Bean Validation annotations such as @NotNull
or @NotBlank to these fields to enforce that they are mandatory and not empty
when persisted.
🛠️ Refactor suggestion
필드 검증 어노테이션 및 제약 조건 추가 권장
과제 데이터의 무결성을 위해 검증 어노테이션을 추가하는 것을 권장합니다.
+ import jakarta.validation.constraints.NotBlank;
+ import jakarta.validation.constraints.Size;
+ import jakarta.validation.constraints.Future;
@Column(name = "title")
+ @NotBlank(message = "과제 제목은 필수입니다")
+ @Size(max = 200, message = "제목은 200자를 초과할 수 없습니다")
private String title;
@Column(name = "content")
+ @Size(max = 5000, message = "내용은 5000자를 초과할 수 없습니다")
private String content;
@Column(name = "start_date")
private LocalDateTime startDate;
@Column(name = "due_date")
+ @Future(message = "마감일은 현재 시간 이후여야 합니다")
private LocalDateTime dueDate;📝 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.
| @Column(name = "title") | |
| private String title; | |
| @Column(name = "content") | |
| private String content; | |
| @Column(name = "start_date") | |
| private LocalDateTime startDate; | |
| @Column(name = "due_date") | |
| private LocalDateTime dueDate; | |
| // Add these imports at the top of the file | |
| import jakarta.validation.constraints.NotBlank; | |
| import jakarta.validation.constraints.Size; | |
| import jakarta.validation.constraints.Future; | |
| @Column(name = "title") | |
| @NotBlank(message = "과제 제목은 필수입니다") | |
| @Size(max = 200, message = "제목은 200자를 초과할 수 없습니다") | |
| private String title; | |
| @Column(name = "content") | |
| @Size(max = 5000, message = "내용은 5000자를 초과할 수 없습니다") | |
| private String content; | |
| @Column(name = "start_date") | |
| private LocalDateTime startDate; | |
| @Column(name = "due_date") | |
| @Future(message = "마감일은 현재 시간 이후여야 합니다") | |
| private LocalDateTime dueDate; |
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java
between lines 30 and 40, the entity fields lack validation annotations to ensure
data integrity. Add appropriate validation annotations such as @NotNull,
@NotBlank, or @Size to the fields title, content, startDate, and dueDate to
enforce constraints like non-nullability and length limits. This will help
maintain the consistency and validity of assignment data.
| public AssignmentAttachmentDto toDto() { | ||
| return AssignmentAttachmentDto.builder() | ||
| .assignmentAttachmentId(assignmentAttachmentId) | ||
| .assignment(assignment) | ||
| .user(user) | ||
| .originalFileName(originalFileName) | ||
| .storedFileName(storedFileName) | ||
| .filePath(filePath) | ||
| .fileSize(fileSize) | ||
| .submitType(submitType) | ||
| .updateDate(updateDate) | ||
| .build(); | ||
| } |
There was a problem hiding this comment.
toDto() 메서드에서 지연 로딩 엔티티 직접 노출
toDto() 메서드에서 assignment와 user 엔티티를 직접 반환하면 세션이 닫힌 후 LazyInitializationException이 발생할 수 있습니다.
DTO에서는 필요한 ID만 포함하도록 수정하세요:
public AssignmentAttachmentDto toDto() {
return AssignmentAttachmentDto.builder()
.assignmentAttachmentId(assignmentAttachmentId)
- .assignment(assignment)
- .user(user)
+ .assignmentId(assignment != null ? assignment.getAssignmentId() : null)
+ .userId(user != null ? user.getUserId() : null)
.originalFileName(originalFileName)
.storedFileName(storedFileName)
.filePath(filePath)
.fileSize(fileSize)
.submitType(submitType)
.updateDate(updateDate)
.build();
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java
between lines 47 and 59, the toDto() method currently returns the full
assignment and user entities, which can cause LazyInitializationException after
the session is closed. Modify the toDto() method to include only the IDs of
assignment and user in the DTO instead of the entire entities to avoid lazy
loading issues.
toDto 메서드에서 엔티티 객체 직접 노출 문제
DTO에 엔티티 객체(assignment, user)를 직접 전달하면 지연 로딩 문제와 순환 참조 위험이 있습니다.
엔티티 대신 ID나 필요한 필드만 전달하도록 수정해주세요:
public AssignmentAttachmentDto toDto() {
return AssignmentAttachmentDto.builder()
.assignmentAttachmentId(assignmentAttachmentId)
- .assignment(assignment)
- .user(user)
+ .assignmentId(assignment != null ? assignment.getAssignmentId() : null)
+ .userId(user != null ? user.getUserId() : null)
.originalFileName(originalFileName)
.storedFileName(storedFileName)
.filePath(filePath)
.fileSize(fileSize)
.submitType(submitType)
.updateDate(updateDate)
.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.
| public AssignmentAttachmentDto toDto() { | |
| return AssignmentAttachmentDto.builder() | |
| .assignmentAttachmentId(assignmentAttachmentId) | |
| .assignment(assignment) | |
| .user(user) | |
| .originalFileName(originalFileName) | |
| .storedFileName(storedFileName) | |
| .filePath(filePath) | |
| .fileSize(fileSize) | |
| .submitType(submitType) | |
| .updateDate(updateDate) | |
| .build(); | |
| } | |
| public AssignmentAttachmentDto toDto() { | |
| return AssignmentAttachmentDto.builder() | |
| .assignmentAttachmentId(assignmentAttachmentId) | |
| .assignmentId(assignment != null ? assignment.getAssignmentId() : null) | |
| .userId(user != null ? user.getUserId() : null) | |
| .originalFileName(originalFileName) | |
| .storedFileName(storedFileName) | |
| .filePath(filePath) | |
| .fileSize(fileSize) | |
| .submitType(submitType) | |
| .updateDate(updateDate) | |
| .build(); | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java
lines 47 to 59, the toDto method currently passes entire entity objects
assignment and user directly to the DTO, which risks lazy loading issues and
circular references. Modify the method to pass only the necessary fields such as
assignmentId and userId (or other required primitive fields) instead of the full
entity objects to the DTO builder, ensuring safer and cleaner data transfer.
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "user_id") | ||
| private UserEntity user; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "assignment_id") | ||
| private Assignment assignment; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
중복 제출 체크 방지를 위한 유니크 제약조건 추가 권장
동일한 사용자가 같은 과제에 대해 여러 개의 체크 레코드를 생성할 수 있어 데이터 무결성 문제가 발생할 수 있습니다.
테이블 레벨에서 유니크 제약조건을 추가하세요:
@Entity
- @Table(name="assignment_check")
+ @Table(name="assignment_check",
+ uniqueConstraints = @UniqueConstraint(
+ columnNames = {"user_id", "assignment_id"}))
@Getter @Setter🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java
around lines 19 to 25, add a unique constraint at the table level to prevent
duplicate submissions by the same user for the same assignment. Modify the
entity's table annotation to include a unique constraint on the combination of
user_id and assignment_id columns to enforce data integrity and avoid multiple
check records for the same user-assignment pair.
🛠️ Refactor suggestion
유니크 제약 조건 추가 권장
같은 사용자가 같은 과제에 대해 중복된 체크 레코드를 가질 수 없도록 유니크 제약 조건을 추가해야 합니다.
@Entity
@Table(name="assignment_check",
+ uniqueConstraints = @UniqueConstraint(
+ name = "uk_assignment_check_user_assignment",
+ columnNames = {"user_id", "assignment_id"}
+ ))🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java
around lines 19 to 25, add a unique constraint to the entity to prevent
duplicate check records for the same user and assignment. Modify the class-level
annotation to include a unique constraint on the combination of user_id and
assignment_id columns, ensuring that the database enforces uniqueness for these
pairs.
| @Column(name="is_submitted") | ||
| private Boolean isSubmitted; | ||
|
|
||
| @Column(name="submitted_at") | ||
| private LocalDateTime submittedAt; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
제출 상태 필드 개선 권장
isSubmitted 필드가 null일 수 있어 비즈니스 로직에서 예상치 못한 동작이 발생할 수 있습니다. 또한 제출 시간과 제출 상태 간의 일관성 검증이 필요합니다.
다음과 같이 개선하세요:
@Column(name="is_submitted")
+ @Builder.Default
private Boolean isSubmitted;
@Column(name="submitted_at")
private LocalDateTime submittedAt;
+
+ @PrePersist
+ @PreUpdate
+ private void validateSubmission() {
+ if (isSubmitted == null) {
+ isSubmitted = false;
+ }
+ if (Boolean.TRUE.equals(isSubmitted) && submittedAt == null) {
+ submittedAt = LocalDateTime.now();
+ }
+ }📝 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.
| @Column(name="is_submitted") | |
| private Boolean isSubmitted; | |
| @Column(name="submitted_at") | |
| private LocalDateTime submittedAt; | |
| @Column(name="is_submitted") | |
| @Builder.Default | |
| private Boolean isSubmitted; | |
| @Column(name="submitted_at") | |
| private LocalDateTime submittedAt; | |
| @PrePersist | |
| @PreUpdate | |
| private void validateSubmission() { | |
| if (isSubmitted == null) { | |
| isSubmitted = false; | |
| } | |
| if (Boolean.TRUE.equals(isSubmitted) && submittedAt == null) { | |
| submittedAt = LocalDateTime.now(); | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java
around lines 27 to 31, the isSubmitted field is a Boolean that can be null,
which may cause unexpected behavior in business logic. Change the isSubmitted
field to a primitive boolean to avoid null values, and implement validation
logic to ensure consistency between isSubmitted and submittedAt fields, such as
submittedAt being non-null only if isSubmitted is true.
🛠️ Refactor suggestion
제출 상태와 제출 시간 간의 일관성 보장 필요
isSubmitted가 true일 때 submittedAt이 반드시 설정되어야 하고, false일 때는 null이어야 합니다.
비즈니스 로직 검증을 위한 메서드를 추가하세요:
+ import jakarta.persistence.PrePersist;
+ import jakarta.persistence.PreUpdate;
@Column(name="is_submitted")
+ @Builder.Default
private Boolean isSubmitted;
@Column(name="submitted_at")
private LocalDateTime submittedAt;
+ @PrePersist
+ @PreUpdate
+ private void validateSubmissionStatus() {
+ if (Boolean.TRUE.equals(isSubmitted) && submittedAt == null) {
+ submittedAt = LocalDateTime.now();
+ } else if (Boolean.FALSE.equals(isSubmitted)) {
+ submittedAt = null;
+ }
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java
around lines 27 to 31, ensure consistency between isSubmitted and submittedAt
fields by adding a validation method. Implement a method that checks if
isSubmitted is true, then submittedAt must be non-null, and if isSubmitted is
false, submittedAt must be null. This method will enforce the business logic
consistency between submission status and submission time.
| if (originalFileName == null || !originalFileName.contains(".")) { | ||
| throw new IllegalArgumentException("파일 이름이 유효하지 않습니다."); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
입력 유효성 검사를 개선하세요.
현재 유효성 검사가 충분하지 않습니다. 빈 문자열, 확장자가 없는 파일, 점으로 끝나는 파일명 등을 고려해야 합니다.
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/global/config/FileUtil.java around lines 7 to
8, the current validation only checks if the filename is null or lacks a dot,
but it misses cases like empty strings, filenames without extensions, or those
ending with a dot. Enhance the validation to also check for empty or blank
strings, ensure the filename contains a dot that is not the last character, and
confirm that the extension part is not empty. Throw an IllegalArgumentException
if any of these conditions fail.
| @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.
🛠️ Refactor suggestion
보안 및 설정 검증 개선이 필요합니다.
현재 구현에서 몇 가지 개선점이 있습니다:
- 보안:
BasicAWSCredentials는 프로덕션 환경에서 권장되지 않습니다. AWS IAM 역할이나 프로파일 사용을 고려해보세요. - 설정 검증: 필수 속성값들의 null 체크가 없습니다.
- 타입 캐스팅:
AmazonS3ClientBuilder.build()의 결과를AmazonS3Client로 캐스팅하는 것은 위험할 수 있습니다.
다음과 같이 개선할 수 있습니다:
+import org.springframework.util.StringUtils;
+import com.amazonaws.services.s3.AmazonS3;
@Bean
- public AmazonS3Client amazonS3Client() {
+ public AmazonS3 amazonS3Client() {
+ if (!StringUtils.hasText(accessKey) || !StringUtils.hasText(secretKey) || !StringUtils.hasText(region)) {
+ throw new IllegalStateException("AWS credentials and region must be configured");
+ }
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
- return (AmazonS3Client) AmazonS3ClientBuilder.standard()
+ return AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.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.
| @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; | |
| // Add these imports at the top of the file | |
| import org.springframework.util.StringUtils; | |
| import com.amazonaws.services.s3.AmazonS3; | |
| @Configuration | |
| public class S3Config { | |
| @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; | |
| @Bean | |
| public AmazonS3 amazonS3Client() { | |
| // Validate that all required properties are set | |
| if (!StringUtils.hasText(accessKey) | |
| || !StringUtils.hasText(secretKey) | |
| || !StringUtils.hasText(region)) { | |
| throw new IllegalStateException("AWS credentials and region must be configured"); | |
| } | |
| BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); | |
| return AmazonS3ClientBuilder.standard() | |
| .withRegion(region) | |
| .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) | |
| .build(); | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/global/config/S3Config.java around lines 13
to 21, improve security by replacing BasicAWSCredentials with AWS IAM role or
profile-based authentication instead of hardcoding keys. Add null or empty
checks for the injected configuration properties to validate essential settings
before use. Avoid casting the result of AmazonS3ClientBuilder.build() to
AmazonS3Client; instead, use the AmazonS3 interface type to hold the client
instance safely.
| public String upload(MultipartFile file, String dirName) { | ||
| String originalName = file.getOriginalFilename(); | ||
| String fileName = createFileName(originalName); | ||
| String filePath = dirName + "/" + fileName; | ||
|
|
||
| ObjectMetadata metadata = new ObjectMetadata(); | ||
| metadata.setContentLength(file.getSize()); | ||
| metadata.setContentType(file.getContentType()); | ||
|
|
||
| try (InputStream inputStream = file.getInputStream()) { | ||
| amazonS3Client.putObject(bucket, filePath, inputStream, metadata); | ||
| } catch (IOException e) { | ||
| throw new RuntimeException("파일 업로드 실패", e); | ||
| } | ||
|
|
||
| return amazonS3Client.getUrl(bucket, filePath).toString(); // 업로드된 파일 URL 반환 | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
파일 업로드 검증 로직 추가 권장
파일 크기, 타입, 확장자에 대한 검증이 없어 보안상 위험할 수 있습니다. 악성 파일 업로드나 과도한 크기의 파일로 인한 문제를 방지하기 위해 검증 로직을 추가하는 것을 권장합니다.
다음과 같은 검증을 추가하세요:
public String upload(MultipartFile file, String dirName) {
// 파일 검증
if (file.isEmpty()) {
throw new IllegalArgumentException("파일이 비어있습니다.");
}
if (file.getSize() > MAX_FILE_SIZE) {
throw new IllegalArgumentException("파일 크기가 제한을 초과합니다.");
}
String originalName = file.getOriginalFilename();
// 기존 로직...
}🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/global/config/S3Uploader.java around lines 22
to 38, the upload method lacks validation for file size, type, and extension,
which poses security risks. Add validation checks at the start of the method to
ensure the file is not empty and its size does not exceed a defined
MAX_FILE_SIZE constant. Throw IllegalArgumentException with appropriate messages
if these validations fail. Optionally, extend validation to check allowed file
types and extensions before proceeding with the upload.
파일 업로드 메서드의 보안 및 안정성 개선이 필요합니다.
다음 보안 및 안정성 문제들을 수정해주세요:
originalName에 대한 null 체크 누락- 파일 확장자 존재 여부 검증 부재
- 디렉토리 경로 검증 부족 (경로 조작 공격 가능성)
- 파일 크기 및 타입 제한 없음
다음과 같이 수정하는 것을 권장합니다:
public String upload(MultipartFile file, String dirName) {
+ if (file == null || file.isEmpty()) {
+ throw new IllegalArgumentException("파일이 비어있습니다.");
+ }
+
+ // 디렉토리 경로 검증
+ if (dirName == null || dirName.contains("..") || dirName.contains("/")) {
+ throw new IllegalArgumentException("유효하지 않은 디렉토리명입니다.");
+ }
+
String originalName = file.getOriginalFilename();
+ if (originalName == null || !originalName.contains(".")) {
+ throw new IllegalArgumentException("유효하지 않은 파일명입니다.");
+ }
+
String fileName = createFileName(originalName);
String filePath = dirName + "/" + fileName;📝 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 upload(MultipartFile file, String dirName) { | |
| String originalName = file.getOriginalFilename(); | |
| String fileName = createFileName(originalName); | |
| String filePath = dirName + "/" + fileName; | |
| ObjectMetadata metadata = new ObjectMetadata(); | |
| metadata.setContentLength(file.getSize()); | |
| metadata.setContentType(file.getContentType()); | |
| try (InputStream inputStream = file.getInputStream()) { | |
| amazonS3Client.putObject(bucket, filePath, inputStream, metadata); | |
| } catch (IOException e) { | |
| throw new RuntimeException("파일 업로드 실패", e); | |
| } | |
| return amazonS3Client.getUrl(bucket, filePath).toString(); // 업로드된 파일 URL 반환 | |
| } | |
| public String upload(MultipartFile file, String dirName) { | |
| if (file == null || file.isEmpty()) { | |
| throw new IllegalArgumentException("파일이 비어있습니다."); | |
| } | |
| // 디렉토리 경로 검증 | |
| if (dirName == null || dirName.contains("..") || dirName.contains("/")) { | |
| throw new IllegalArgumentException("유효하지 않은 디렉토리명입니다."); | |
| } | |
| String originalName = file.getOriginalFilename(); | |
| if (originalName == null || !originalName.contains(".")) { | |
| throw new IllegalArgumentException("유효하지 않은 파일명입니다."); | |
| } | |
| String fileName = createFileName(originalName); | |
| String filePath = dirName + "/" + fileName; | |
| ObjectMetadata metadata = new ObjectMetadata(); | |
| metadata.setContentLength(file.getSize()); | |
| metadata.setContentType(file.getContentType()); | |
| try (InputStream inputStream = file.getInputStream()) { | |
| amazonS3Client.putObject(bucket, filePath, inputStream, metadata); | |
| } catch (IOException e) { | |
| throw new RuntimeException("파일 업로드 실패", e); | |
| } | |
| return amazonS3Client.getUrl(bucket, filePath).toString(); // 업로드된 파일 URL 반환 | |
| } |
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/global/config/S3Uploader.java lines 22 to 38,
the upload method lacks security and stability checks. Fix this by adding a null
check for originalName before using it, validate that the file has a proper
extension to prevent invalid files, sanitize and validate dirName to avoid path
traversal or manipulation attacks, and enforce file size and content type
restrictions to allow only safe and expected files. Implement these validations
before proceeding with file upload to enhance security and reliability.
| private String createFileName(String originalName) { | ||
| String ext = originalName.substring(originalName.lastIndexOf(".")); | ||
| return UUID.randomUUID().toString() + ext; | ||
| } |
There was a problem hiding this comment.
파일 확장자 추출 로직에서 잠재적인 런타임 예외 발생 가능
originalName.lastIndexOf(".")가 -1을 반환할 경우 substring 메서드에서 StringIndexOutOfBoundsException이 발생할 수 있습니다.
다음과 같이 수정하여 안전하게 처리하세요:
private String createFileName(String originalName) {
- String ext = originalName.substring(originalName.lastIndexOf("."));
+ if (originalName == null || !originalName.contains(".")) {
+ return UUID.randomUUID().toString();
+ }
+ String ext = originalName.substring(originalName.lastIndexOf("."));
return UUID.randomUUID().toString() + ext;
}📝 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 String createFileName(String originalName) { | |
| String ext = originalName.substring(originalName.lastIndexOf(".")); | |
| return UUID.randomUUID().toString() + ext; | |
| } | |
| private String createFileName(String originalName) { | |
| if (originalName == null || !originalName.contains(".")) { | |
| return UUID.randomUUID().toString(); | |
| } | |
| String ext = originalName.substring(originalName.lastIndexOf(".")); | |
| return UUID.randomUUID().toString() + ext; | |
| } |
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/global/config/S3Uploader.java around lines 40
to 43, the method createFileName extracts the file extension using substring
without checking if the originalName contains a dot, which can cause
StringIndexOutOfBoundsException if lastIndexOf returns -1. Fix this by adding a
check to see if the dot exists; if it does, extract the extension as before,
otherwise handle the case safely by either returning a filename without an
extension or appending a default extension.
파일명 생성 메서드의 예외 처리가 부족합니다.
createFileName 메서드에서 파일 확장자가 없는 경우 StringIndexOutOfBoundsException이 발생할 수 있습니다.
다음과 같이 수정해주세요:
private String createFileName(String originalName) {
+ if (originalName == null || !originalName.contains(".")) {
+ throw new IllegalArgumentException("파일 확장자가 없습니다.");
+ }
String ext = originalName.substring(originalName.lastIndexOf("."));
return UUID.randomUUID().toString() + ext;
}📝 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 String createFileName(String originalName) { | |
| String ext = originalName.substring(originalName.lastIndexOf(".")); | |
| return UUID.randomUUID().toString() + ext; | |
| } | |
| private String createFileName(String originalName) { | |
| if (originalName == null || !originalName.contains(".")) { | |
| throw new IllegalArgumentException("파일 확장자가 없습니다."); | |
| } | |
| String ext = originalName.substring(originalName.lastIndexOf(".")); | |
| return UUID.randomUUID().toString() + ext; | |
| } |
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/global/config/S3Uploader.java around lines 40
to 43, the createFileName method does not handle cases where the original file
name lacks an extension, causing a StringIndexOutOfBoundsException. Modify the
method to check if the originalName contains a dot before extracting the
extension; if not, handle it gracefully by either returning a UUID without an
extension or adding a default extension. This prevents exceptions and ensures
robust file name generation.
| accessKey: ${AWS_ACCESS_KEY} | ||
| secretKey: ${AWS_SECRET_KEY} | ||
| s3: | ||
| bucket : ${AWS_BUCKET_NAME} |
There was a problem hiding this comment.
YAML 형식 오류를 수정해주세요.
콜론 앞에 불필요한 공백이 있습니다.
- bucket : ${AWS_BUCKET_NAME}
+ bucket: ${AWS_BUCKET_NAME}📝 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.
| bucket : ${AWS_BUCKET_NAME} | |
| bucket: ${AWS_BUCKET_NAME} |
🧰 Tools
🪛 YAMLlint (1.37.1)
[warning] 59-59: too many spaces before colon
(colons)
🤖 Prompt for AI Agents
In src/main/resources/application.yaml at line 59, remove the unnecessary space
before the colon in the key-value pair to fix the YAML format error. Change
"bucket : ${AWS_BUCKET_NAME}" to "bucket: ${AWS_BUCKET_NAME}" ensuring no spaces
precede the colon.
| @Column(name = "start_date") | ||
| private LocalDateTime startDate; | ||
|
|
||
| @Column(name = "due_date") | ||
| private LocalDateTime dueDate; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
과제 날짜 검증 로직 추가 필요
시작일이 마감일보다 늦는 경우를 방지하는 검증이 필요합니다.
엔티티에 검증 메서드를 추가하거나 서비스 레이어에서 검증하세요:
@PrePersist
@PreUpdate
private void validateDates() {
if (startDate != null && dueDate != null && startDate.isAfter(dueDate)) {
throw new IllegalArgumentException("시작일은 마감일보다 늦을 수 없습니다");
}
}🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java
around lines 36 to 40, add a validation method to ensure the startDate is not
after the dueDate. Implement a private method annotated with @PrePersist and
@PreUpdate that checks if both dates are non-null and throws an
IllegalArgumentException if startDate is after dueDate. This validation can be
added inside the entity class to enforce date consistency before saving or
updating.
| @Column(name = "file_size") | ||
| private Integer fileSize; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
파일 크기 데이터 타입을 Long으로 변경하는 것을 권장합니다.
Integer 타입으로는 약 2GB까지만 표현 가능하여, 큰 파일의 경우 오버플로우가 발생할 수 있습니다.
@Column(name = "file_size")
- private Integer fileSize;
+ private Long fileSize;해당 DTO 클래스에서도 동일하게 수정이 필요합니다.
📝 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.
| @Column(name = "file_size") | |
| private Integer fileSize; | |
| @Column(name = "file_size") | |
| private Long fileSize; |
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java
at lines 37-38, change the data type of the fileSize field from Integer to Long
to support file sizes larger than 2GB and prevent overflow. Also, update the
corresponding DTO class to use Long for the file size field to maintain
consistency.
| @Column(name="is_submitted") | ||
| private Boolean isSubmitted; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Boolean 필드 기본값 설정
isSubmitted 필드에 기본값을 설정하여 null 상태를 방지하세요.
@Column(name="is_submitted")
+ @Builder.Default
- private Boolean isSubmitted;
+ private Boolean isSubmitted = false;또는 데이터베이스 레벨에서도 기본값을 설정할 수 있습니다:
- @Column(name="is_submitted")
+ @Column(name="is_submitted", nullable = false, columnDefinition = "BOOLEAN DEFAULT 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.
| @Column(name="is_submitted") | |
| private Boolean isSubmitted; | |
| @Column(name="is_submitted") | |
| @Builder.Default | |
| private Boolean isSubmitted = false; |
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java
at lines 27-28, the Boolean field isSubmitted lacks a default value, which can
lead to null issues. Initialize isSubmitted with a default value, such as false,
directly in the field declaration or set a default value at the database column
level using the appropriate annotation or schema definition to prevent null
states.
| try { | ||
| Long userId = jwtTokenTaker(request); | ||
| assignmentService.createAssignment(userId, classId, requestDto, files); | ||
| return ResponseEntity.status(HttpStatus.CREATED).body("과제 생성 완료"); | ||
| } catch(Exception e) { | ||
| log.error(e.getMessage()); | ||
| return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
예외 처리 개선 필요
일반적인 Exception을 잡는 대신 구체적인 예외를 처리하고, 스택 트레이스를 포함하여 로깅해야 합니다.
- } catch(Exception e) {
- log.error(e.getMessage());
+ } catch(EntityNotFoundException e) {
+ log.error("Entity not found: ", e);
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
+ } catch(AccessDeniedException e) {
+ log.error("Access denied: ", e);
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getMessage());
+ } catch(Exception e) {
+ log.error("Unexpected error: ", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}Also applies to: 78-93
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java
around lines 60 to 68 and 78 to 93, replace the generic catch of Exception with
specific exception types relevant to the operations performed. Update the
logging to include the full stack trace by passing the exception object to the
logger instead of just the message. This will improve error handling clarity and
provide more detailed logs for debugging.
| try { | ||
| Resource file = fileService.downloadFile(attachmentId, userId); | ||
| AssignmentAttachmentDto assignmentDto = assignmentService.findById(attachmentId); | ||
| String fullPath = assignmentDto.getFilePath(); | ||
| UrlResource resource = new UrlResource("file:" + fullPath); | ||
|
|
||
| String encodedFileName = UriUtils.encode(fullPath.split("_")[1], StandardCharsets.UTF_8); | ||
| String contentDisposition = "attachment; filename=\"" + encodedFileName + "\""; | ||
|
|
||
| return ResponseEntity.ok() | ||
| .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) | ||
| .body(resource); |
There was a problem hiding this comment.
파일 다운로드 로직 오류
S3에서 파일을 가져왔지만 실제로는 로컬 파일 경로로 UrlResource를 생성하여 반환하고 있습니다. 또한 파일명 파싱 시 _로 분할하는 로직은 파일명에 _가 없거나 여러 개 있을 경우 오류가 발생할 수 있습니다.
- Resource file = fileService.downloadFile(attachmentId, userId);
- AssignmentAttachmentDto assignmentDto = assignmentService.findById(attachmentId);
- String fullPath = assignmentDto.getFilePath();
- UrlResource resource = new UrlResource("file:" + fullPath);
-
- String encodedFileName = UriUtils.encode(fullPath.split("_")[1], StandardCharsets.UTF_8);
+ Resource file = fileService.downloadFile(attachmentId, userId);
+ AssignmentAttachmentDto assignmentDto = assignmentService.findById(attachmentId);
+
+ String originalFileName = assignmentDto.getOriginalFileName();
+ String encodedFileName = UriUtils.encode(originalFileName, StandardCharsets.UTF_8);
String contentDisposition = "attachment; filename=\"" + encodedFileName + "\"";
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
- .body(resource);
+ .body(file);📝 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.
| try { | |
| Resource file = fileService.downloadFile(attachmentId, userId); | |
| AssignmentAttachmentDto assignmentDto = assignmentService.findById(attachmentId); | |
| String fullPath = assignmentDto.getFilePath(); | |
| UrlResource resource = new UrlResource("file:" + fullPath); | |
| String encodedFileName = UriUtils.encode(fullPath.split("_")[1], StandardCharsets.UTF_8); | |
| String contentDisposition = "attachment; filename=\"" + encodedFileName + "\""; | |
| return ResponseEntity.ok() | |
| .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) | |
| .body(resource); | |
| try { | |
| Resource file = fileService.downloadFile(attachmentId, userId); | |
| AssignmentAttachmentDto assignmentDto = assignmentService.findById(attachmentId); | |
| String originalFileName = assignmentDto.getOriginalFileName(); | |
| String encodedFileName = UriUtils.encode(originalFileName, StandardCharsets.UTF_8); | |
| String contentDisposition = "attachment; filename=\"" + encodedFileName + "\""; | |
| return ResponseEntity.ok() | |
| .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) | |
| .body(file); |
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java
around lines 78 to 89, the file download logic incorrectly creates a UrlResource
from a local file path instead of using the S3 resource returned by
fileService.downloadFile. Also, the filename extraction by splitting on '_' is
fragile and can fail if the filename contains multiple or no underscores. Fix
this by returning the Resource object directly from fileService without
reconstructing a UrlResource, and improve filename extraction by using a more
reliable method such as extracting the filename from the full path or metadata
without relying on splitting by '_'.
|
|
||
| public List<GetAssignmentResponseDto> getAllAssignment(Long classId, Long userId) { | ||
|
|
||
| UserEntity user = validated(classId, userId); |
There was a problem hiding this comment.
validated 메서드 호출 시 파라미터 순서 오류
validated 메서드는 (userId, classId) 순서로 파라미터를 받는데, 잘못된 순서로 호출하고 있습니다.
- UserEntity user = validated(classId, userId);
+ UserEntity user = validated(userId, classId);📝 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.
| UserEntity user = validated(classId, userId); | |
| UserEntity user = validated(userId, classId); |
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java
at line 107, the validated method is called with parameters in the wrong order.
The method expects (userId, classId) but currently receives (classId, userId).
Swap the arguments to call validated(userId, classId) to fix the parameter
order.
| .user(user) | ||
| .title(requestDto.getTitle()) | ||
| .content(requestDto.getContent()) | ||
| .startDate(requestDto.getStartData()) |
There was a problem hiding this comment.
DTO 필드명 오타
requestDto.getStartData()는 requestDto.getStartDate()여야 합니다.
- .startDate(requestDto.getStartData())
+ .startDate(requestDto.getStartDate())📝 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.
| .startDate(requestDto.getStartData()) | |
| .startDate(requestDto.getStartDate()) |
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java
at line 145, there is a typo in the DTO field name where
requestDto.getStartData() is used. Change this method call to
requestDto.getStartDate() to correctly reference the start date field.
| public ClassRoomAllInfoDto getAllInfo(Long classId) { | ||
| ClassRoom classRoom = classRoomRepository.findById(classId) | ||
| .orElseThrow(() -> new IllegalArgumentException("해당 수업이 존재하지 않습니다.")); | ||
|
|
||
| List<DirectoryAllInfoDto> directoryDtoList = classRoom.getDirectoryList().stream() | ||
| .sorted(Comparator.comparingInt(Directory::getDirectoryOrder)) // directoryOrder 기준 정렬 | ||
| .map(directory -> { | ||
| List<DocumentAllInfoDto> documentDtoList = directory.getDocumentList().stream() | ||
| .sorted(Comparator.comparing(Document::getCreatedAt)) // createdAt 기준 정렬 | ||
| .map(doc -> DocumentAllInfoDto.builder() | ||
| .documentId(doc.getDocumentId()) | ||
| .title(doc.getTitle()) | ||
| .createdAt(doc.getCreatedAt()) | ||
| .build()) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| return DirectoryAllInfoDto.builder() | ||
| .directoryId(directory.getDirectoryId()) | ||
| .directoryName(directory.getName()) | ||
| .directoryOrder(directory.getDirectoryOrder()) | ||
| .documentList(documentDtoList) | ||
| .build(); | ||
| }).collect(Collectors.toList()); | ||
| List<UserEntity> findUsers = classRoomUserRepository.findUsersByClassRoomId(classId); | ||
|
|
||
| List<String> teacherNames = findUsers.stream() | ||
| .filter(user -> user.getRole() == Role.TEACHER) | ||
| .map(UserEntity::getUsername) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| return ClassRoomAllInfoDto.builder() | ||
| .classRoomId(classRoom.getClassRoomId()) | ||
| .classRoomName(classRoom.getName()) | ||
| .description(classRoom.getDescription()) | ||
| .directoryList(directoryDtoList) | ||
| .teacherNames(teacherNames) | ||
| .build(); | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
N+1 쿼리 문제 가능성을 확인하세요.
이 메서드는 교실, 디렉토리, 문서를 중첩해서 조회하므로 N+1 쿼리 문제가 발생할 수 있습니다. 적절한 fetch join이나 @entitygraph를 사용하고 있는지 확인이 필요합니다.
다음 스크립트를 실행하여 ClassRoom 엔티티의 관계 설정을 확인하세요:
🏁 Script executed:
#!/bin/bash
# ClassRoom 엔티티의 디렉토리 관계 확인
ast-grep --pattern 'class ClassRoom {
$$$
@OneToMany($$$)
$$$directoryList$$$
$$$
}'
# Directory 엔티티의 문서 관계 확인
ast-grep --pattern 'class Directory {
$$$
@OneToMany($$$)
$$$documentList$$$
$$$
}'Length of output: 188
🏁 Script executed:
#!/bin/bash
# OneToMany 매핑 확인
rg "@OneToMany" -n -C2
# fetch 속성 확인
rg "@OneToMany.*fetch" -n
# @EntityGraph 사용 확인
rg "@EntityGraph" -n
# ClassRoom 엔티티 파일 내용 확인 (최대 100줄)
rg -l "class ClassRoom" | xargs -r sed -n '1,100p'
# Directory 엔티티 파일 내용 확인 (최대 100줄)
rg -l "class Directory" | xargs -r sed -n '1,100p'
# ClassRoomRepository 파일 확인 (최대 200줄)
rg -l "interface ClassRoomRepository" | xargs -r sed -n '1,200p'Length of output: 10870
N+1 쿼리 문제 및 fetch-join 적용 검토 필요
현재 getAllInfo()는 ClassRoom → Directory → Document 관계를 모두 LAZY 로딩으로 처리해, 각 컬렉션 접근 시 추가 쿼리가 발생할 수 있습니다. 성능 최적화를 위해 아래 사항을 검토해 주세요:
- ClassRoomRepository에 @entitygraph 또는 JPQL fetch-join(@query) 추가
- Service 메서드에 @transactional(readOnly = true) 적용 고려
예시( ClassRoomRepository.java )
@Repository
public interface ClassRoomRepository extends JpaRepository<ClassRoom, Long> {
@EntityGraph(attributePaths = {"directoryList", "directoryList.documentList"})
@Query("SELECT c FROM ClassRoom c WHERE c.classRoomId = :classId")
Optional<ClassRoom> findByIdWithContents(@Param("classId") Long classId);
}그리고 Service에서는 findByIdWithContents 사용으로 한 번에 데이터를 로드하세요.
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java
lines 88 to 125, the getAllInfo method causes N+1 query issues due to lazy
loading of ClassRoom's directories and documents. To fix this, add a method in
ClassRoomRepository using @EntityGraph or a JPQL fetch-join query to eagerly
load directoryList and documentList in one query. Then, update getAllInfo to use
this new repository method to fetch all data at once. Also, annotate the service
method with @Transactional(readOnly = true) to optimize transaction handling for
read operations.
| return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); | ||
| } catch (MalformedURLException e) { | ||
|
|
||
| String encodedFileName = UriUtils.encode(fullPath.split("_")[1], StandardCharsets.UTF_8); |
There was a problem hiding this comment.
파일명 추출 로직의 안정성을 개선하세요.
split("_")[1]은 파일명에 언더스코어가 없거나 여러 개인 경우 오류가 발생할 수 있습니다. 더 안전한 방법을 사용하세요.
- String encodedFileName = UriUtils.encode(fullPath.split("_")[1], StandardCharsets.UTF_8);
+ String fileName = fullPath.substring(fullPath.lastIndexOf(File.separator) + 1);
+ int underscoreIndex = fileName.indexOf("_");
+ String originalFileName = underscoreIndex >= 0 ? fileName.substring(underscoreIndex + 1) : fileName;
+ String encodedFileName = UriUtils.encode(originalFileName, StandardCharsets.UTF_8);📝 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.
| String encodedFileName = UriUtils.encode(fullPath.split("_")[1], StandardCharsets.UTF_8); | |
| // 이전: String encodedFileName = UriUtils.encode(fullPath.split("_")[1], StandardCharsets.UTF_8); | |
| String fileName = fullPath.substring(fullPath.lastIndexOf(File.separator) + 1); | |
| int underscoreIndex = fileName.indexOf("_"); | |
| String originalFileName = underscoreIndex >= 0 | |
| ? fileName.substring(underscoreIndex + 1) | |
| : fileName; | |
| String encodedFileName = UriUtils.encode(originalFileName, StandardCharsets.UTF_8); |
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java
at line 115, the current filename extraction using split("_")[1] is unsafe
because it can throw errors if the filename has no underscore or multiple
underscores. Replace this with a more robust method such as finding the last
underscore index and extracting the substring after it, or using a safer parsing
approach that checks for the presence and position of underscores before
extracting the filename.
| public void delete(String filePath) { | ||
| amazonS3Client.deleteObject(bucket, filePath); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
파일 삭제 시 경로 검증이 필요합니다.
delete 메서드에서 filePath 매개변수에 대한 검증이 없어 보안상 위험할 수 있습니다.
public void delete(String filePath) {
+ if (filePath == null || filePath.trim().isEmpty()) {
+ throw new IllegalArgumentException("파일 경로가 유효하지 않습니다.");
+ }
+
+ // 경로 조작 공격 방지
+ if (filePath.contains("..") || filePath.startsWith("/")) {
+ throw new IllegalArgumentException("유효하지 않은 파일 경로입니다.");
+ }
+
amazonS3Client.deleteObject(bucket, filePath);
}📝 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 void delete(String filePath) { | |
| amazonS3Client.deleteObject(bucket, filePath); | |
| } | |
| public void delete(String filePath) { | |
| if (filePath == null || filePath.trim().isEmpty()) { | |
| throw new IllegalArgumentException("파일 경로가 유효하지 않습니다."); | |
| } | |
| // 경로 조작 공격 방지 | |
| if (filePath.contains("..") || filePath.startsWith("/")) { | |
| throw new IllegalArgumentException("유효하지 않은 파일 경로입니다."); | |
| } | |
| amazonS3Client.deleteObject(bucket, filePath); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/global/config/S3Uploader.java around lines 45
to 47, the delete method lacks validation for the filePath parameter, which
poses a security risk. Add validation logic to check that filePath is not null,
not empty, and conforms to expected path patterns before calling
amazonS3Client.deleteObject. If validation fails, throw an appropriate exception
or handle the error to prevent unauthorized or malformed deletions.
Feat/#6
✅ 작업 내용
🔗 관련 이슈
Close #6
📌 체크리스트
Summary by CodeRabbit
신규 기능
버그 수정
환경 설정
기타