Skip to content

Bug/#66#67

Merged
Hgyeol merged 5 commits intodevelopfrom
bug/#66
Sep 3, 2025
Merged

Bug/#66#67
Hgyeol merged 5 commits intodevelopfrom
bug/#66

Conversation

@fixgramwork
Copy link
Member

@fixgramwork fixgramwork commented Sep 3, 2025

모든 ID -> UUID로 변경 및 table이름 단수로 통일

Summary by CodeRabbit

  • Refactor
    • 전반적인 API 식별자를 Long에서 UUID로 전환했습니다(사용자, 클래스룸, 과제, 문서, 제출물 등). 클라이언트는 요청/응답에서 UUID를 사용해야 합니다.
    • 인증 토큰(userId)과 관련 흐름이 UUID에 맞춰 일관화되었습니다.
  • Tests
    • 테스트들을 UUID 기반으로 업데이트했으며 일부 테스트 초기화 값 관련 수정 필요성이 발견되었습니다.
  • Chores
    • 프로덕션 JPA 스키마 전략을 update로 변경해 엔터티 변경을 자동 반영합니다.

@fixgramwork fixgramwork self-assigned this Sep 3, 2025
@fixgramwork fixgramwork added the 개발 개발과 관련된 이슈 label Sep 3, 2025
@fixgramwork fixgramwork linked an issue Sep 3, 2025 that may be closed by this pull request
@fixgramwork fixgramwork added the 버그 기능이 정상적으로 작동하지 않을 떄 이슈 label Sep 3, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 3, 2025

Walkthrough

전역적으로 식별자 타입을 Long에서 UUID로 변경. 엔티티(PK), 리포지토리 제네릭, 서비스/컨트롤러/DTO 파라미터 및 반환, JWT 유틸/필터를 UUID 기반으로 전환하고 prod 프로필의 JPA ddl-auto를 validate→update로 변경. 일부 테스트의 UUID 초기화 불일치 존재.

Changes

Cohort / File(s) Change Summary
Assignment API & DTOs
src/main/java/.../assignment/api/AssignmentCommandController.java, .../AssignmentQueryController.java, .../api/dto/request/CreateAssignmentDto.java, .../api/dto/response/AssignmentResponseDto.java, .../api/dto/response/GetAllAssignmentDto.java, .../api/dto/response/SubmissionCheck.java
공개 엔드포인트 및 DTO의 ID 타입 Long → UUID로 변경(파라미터/반환). java.util.UUID import 추가.
Assignment Services
.../assignment/application/AssignmentCommandService.java, .../AssignmentQueryService.java
서비스 공개 API의 ID(Long) → UUID로 전부 변경; 일부 반환 타입(Long→UUID) 조정.
Assignment Domain & Repos
.../assignment/domain/Assignment.java, .../AssignmentAttachment.java, .../persistence/AssignmentRepository.java, .../AssignmentAttachmentRepository.java
엔티티 PK 및 생성전략 GenerationType.IDENTITYGenerationType.UUID, 컬럼 updatable=false 설정; 리포지토리 제네릭 ID → UUID.
Classroom Controller/Service/Repo/DTO
.../classroom/presentation/ClassRoomController.java, .../service/ClassRoomService.java, .../domain/repository/ClassRoomRepository.java, .../presentation/dto/ClassRoomAllInfoDto.java, .../ClassRoomCardDto.java, .../ClassRoomDto.java
controller/service/repo/DTO 전반의 userId/classId 타입 Long → UUID로 변경.
Classroom Domain
.../classroom/domain/ClassRoom.java
PK Long → UUID, 생성전략 → GenerationType.UUID, 컬럼 updatable=false 등 매핑 업데이트.
ClassroomUser Domain/Repo/Service
.../classroomuser/domain/ClassRoomUser.java, .../domain/repository/ClassRoomUserRepository.java, .../application/ClassroomUserService.java
PK 및 모든 ID 인자 Long → UUID. 리포지토리 제네릭 변경, 쿼리 메서드 파라미터를 UUID로 갱신, findAllByClassRoom/existsByClassRoomAndUser 추가.
Directory Domain/Repo/DTO/Service
.../directory/domain/Directory.java, .../domain/repository/DirectoryRepository.java, .../presentation/dto/DirectoryAllInfoDto.java, .../DirectoryDto.java, .../RequestDirectoryDto.java, .../service/DirectoryService.java
Directory PK 및 관련 DTO/서비스/리포지토리 ID Long → UUID. 삭제/검색 메서드 파라미터 UUID로 변경.
Document Domain/Repo/Controller/DTO/Service
.../document/domain/Document.java, .../domain/repository/DocumentRepository.java, .../presentation/DocumentController.java, .../presentation/dto/DocumentAllInfoDto.java, .../DocumentDto.java, .../RequestDocumentDto.java, .../service/DocumentService.java
문서 엔티티 PK 및 컨트롤러/서비스/DTO의 ID Long → UUID. 생성전략 UUID, 컬럼 updatable=false로 변경.
Submission API/Services
.../submission/api/SubmissionCommandController.java, .../SubmissionQueryController.java, .../application/SubmissionCommandService.java, .../application/SubmissionQueryService.java
제출 관련 엔드포인트 및 서비스의 userId/assignment/submission/attachment ID를 Long → UUID로 변경.
Submission Domain & Repos
.../submission/domain/Submission.java, .../SubmissionAttachment.java, .../persistence/SubmissionRepository.java, .../SubmissionAttachmentRepository.java
엔티티 PK 및 생성전략 Long→UUID, 리포지토리 제네릭 ID → UUID, 컬럼 제약 업데이트.
User Domain/Repo/DTO/Service/Presentation
.../user/domain/UserEntity.java, .../domain/repository/UserRepository.java, .../presentation/dto/UserDto.java, .../presentation/dto/CustomOAuth2User.java, .../service/UserService.java, .../presentation/TestController.java
userId 타입 Long → UUID로 전환. 리포지토리 제네릭 변경 및 DTO/서비스/테스트 시그니처 업데이트.
Global Auth/JWT
.../global/common/resolver/CurrentUserArgumentResolver.java, .../global/config/CustomSuccessHandler.java, .../global/config/JWTFilter.java, .../global/config/JWTUtil.java, .../global/security/jwt/RefreshTokenService.java
@currentuser 해석 및 JWT 유틸의 getUserId/createJwt 시그니처 Long → UUID로 변경; 필터/핸들러에서 UUID 사용으로 일치.
Config
src/main/resources/application-prod.yml
spring.jpa.hibernate.ddl-auto 설정: validateupdate.
Test
src/test/java/.../ClassRoomControllerTest.java
테스트에서 userId 타입을 UUID로 변경 시도; 초기값으로 1L 유지되어 타입 불일치 가능.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Feat/#5 #48 — Assignment/Submission 레이어에서 ID를 UUID로 전환하는 변경과 직접적으로 경로·파일군이 중복됩니다.
  • Feat/#6 #43 — Assignment 도메인·컨트롤러 변경과 연계되어 UUID 마이그레이션과 관련성이 높습니다.
  • Feat/#6 #36 — ClassRoom 관련 코드 변경(엔티티/컨트롤러/서비스)과 중복 또는 충돌 가능성이 큽니다.

Poem

깡충깡충, 토끼가 말하네:
숫자 긴 줄은 안녕, UUID가 왔네.
엔티티, 컨트롤러, 토큰까지 바꿨지롱.
스키마는 update로 반짝—삑!
머지 버튼에 귀를 기울이며 🐇✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bug/#66

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@fixgramwork fixgramwork moved this to 🛠 In Progress in clue_ver_1 Sep 3, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 24

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (14)
src/main/java/hello/cluebackend/domain/user/presentation/TestController.java (1)

25-28: 응답 메시지와 실제 동작 불일치(리프레시 미발급).
본문은 refresh 토큰 발급을 언급하지만 실제로는 access만 발급/전달됩니다. 사용자 혼란을 초래합니다. 메시지 수정 또는 실제 refresh 발급 로직 추가 중 하나로 정합성 맞춰주세요.

메시지만 즉시 정정하려면:

-                .body("JWT access token and refresh token issued for dev use.");
+                .body("JWT access token issued for dev use.");
src/test/java/hello/cluebackend/domain/classroom/presentation/ClassRoomControllerTest.java (1)

46-54: 테스트 목 데이터의 classRoomId를 UUID로 변경
ClassRoomCardDtoclassRoomId가 UUID로 정의되어 있으므로, 테스트 코드에서 .classRoomId(1L) 대신 UUID.randomUUID() 또는 적절한 UUID 값을 사용하도록 수정해주세요.

src/main/java/hello/cluebackend/global/security/jwt/RefreshTokenService.java (1)

83-89: Refresh 토큰 쿠키: Secure 미설정 및 TTL 불일치 (보안/정합성 이슈)

HTTPS 환경에서 Secure 미설정은 토큰 노출 위험이 큽니다. 또한 쿠키 Max-Age(7일)와 실제 refresh JWT/Redis TTL(1일)이 불일치합니다. 최소한 아래 변경을 권장합니다.

적용 diff:

-        cookie.setMaxAge(7 * 24  * 60 * 60);
-        // cookie.setSecure(true);
+        cookie.setMaxAge(24 * 60 * 60); // refresh JWT/Redis TTL과 정합성 맞춤 (1일)
+        cookie.setSecure(true); // HTTPS 전송만 허용

추가 권장(선택): SameSite 지정 및 일관 헤더 출력

  • Spring ResponseCookie 사용해 SameSite=Lax(또는 크로스 사이트 필요 시 None+Secure) 설정.
  • 예시:
ResponseCookie rc = ResponseCookie.from("refresh_token", newRefreshToken)
  .httpOnly(true).secure(true).path("/").sameSite("Lax").maxAge(Duration.ofDays(1)).build();
response.addHeader("Set-Cookie", rc.toString());
src/main/java/hello/cluebackend/domain/user/presentation/dto/CustomOAuth2User.java (1)

22-25: getAttributes()가 null 반환 — NPE 위험

일부 보안 컴포넌트/핸들러는 속성 맵이 null이 아님을 가정합니다. 최소한 빈 맵 반환으로 NPE를 방지하세요.

적용 diff:

-    public Map<String, Object> getAttributes() {
-        return null;
-    }
+    public Map<String, Object> getAttributes() {
+        return java.util.Collections.emptyMap();
+    }
src/main/java/hello/cluebackend/domain/directory/service/DirectoryService.java (1)

44-50: 불필요한 try/catch 제거 및 트랜잭션 명시

현재 catch 후 재던지기만 하여 무의미합니다. 삭제는 트랜잭션 경계 내에서 수행하세요. 존재하지 않는 ID 시 EmptyResultDataAccessException을 404로 매핑하는 전략도 고려 바랍니다.

-    public void deleteById(UUID directoryId) {
-        try {
-            directoryRepository.deleteById(directoryId);
-        } catch(Exception e) {
-            throw e;
-        }
-    }
+    @Transactional
+    public void deleteById(UUID directoryId) {
+        directoryRepository.deleteById(directoryId);
+    }

메서드 외부 변경(참고):

// import 추가
import org.springframework.transaction.annotation.Transactional;
src/main/java/hello/cluebackend/global/config/JWTFilter.java (1)

49-68: JWT 파싱 예외 처리 추가로 500 → 401 전환

jwtUtil 호출 중 파싱 예외가 발생하면 500 위험이 있습니다. try/catch로 401을 반환하도록 방어하세요.

-        String category = jwtUtil.getCategory(accessToken);
-        if(!category.equals("access")) {
-            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
-            return;
-        }
-
-        String username = jwtUtil.getUsername(accessToken);
-        Role role = jwtUtil.getRole(accessToken);
-        UUID userId = jwtUtil.getUserId(accessToken);
-
-        UserDto userDTO = new UserDto();
-        userDTO.setUsername(username);
-        userDTO.setRole(role);
-        userDTO.setUserId(userId);
-
-        CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDTO);
-
-        Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities());
-
-        SecurityContextHolder.getContext().setAuthentication(authToken);
+        try {
+            String category = jwtUtil.getCategory(accessToken);
+            if (!"access".equals(category)) {
+                response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+                return;
+            }
+
+            String username = jwtUtil.getUsername(accessToken);
+            Role role = jwtUtil.getRole(accessToken);
+            UUID userId = jwtUtil.getUserId(accessToken);
+
+            UserDto userDTO = new UserDto();
+            userDTO.setUsername(username);
+            userDTO.setRole(role);
+            userDTO.setUserId(userId);
+
+            CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDTO);
+            Authentication authToken = new UsernamePasswordAuthenticationToken(
+                customOAuth2User, null, customOAuth2User.getAuthorities());
+            SecurityContextHolder.getContext().setAuthentication(authToken);
+        } catch (Exception e) {
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java (1)

30-33: enum 클래스명 대문자 시작으로 변경
submission/domain/fileType.java 파일명을 FileType.java로, enum 선언을 public enum FileType { FILE, URL } 로 수정 후 엔티티·DTO·서비스·Q 클래스 내 fileType 모든 참조를 FileType(패키지: hello.cluebackend.domain.submission.domain.FileType)로 업데이트하고 전역 검색(rg \bfileType\b)으로 누락된 곳 없이 수정하세요.

src/main/java/hello/cluebackend/global/config/JWTUtil.java (1)

54-64: JWT claim에 UUID를 문자열로 저장 권장

현재 UUID 객체를 직접 claim에 저장하고 있습니다. JWT 표준과의 호환성 및 디버깅 편의성을 위해 문자열로 변환하여 저장하는 것이 좋습니다.

다음과 같이 수정하세요:

 public String createJwt(String category, UUID userId, String username, String role, Long expiredMs) {
     return  Jwts.builder()
             .claim("category", category)
-            .claim("userId", userId)
+            .claim("userId", userId.toString())
             .claim("username", username)
             .claim("role", role)
             .issuedAt(new Date(System.currentTimeMillis()))
             .expiration(new Date(System.currentTimeMillis() + expiredMs))
             .signWith(secretKey)
             .compact();
 }
src/main/java/hello/cluebackend/domain/document/service/DocumentService.java (1)

132-136: 디버깅용 System.out.println 제거

Line 133, 136에 디버깅용 System.out.println이 포함되어 있습니다. 프로덕션 코드에는 적합하지 않으므로 제거하거나 로거를 사용하세요.

 public void updateDocument(UUID classRoomId, UUID directoryId, List<RequestDocumentDto> requestDocumentDto, List<MultipartFile> files) {
-    System.out.println("directoryId = " + directoryId);
+    log.debug("directoryId = {}", directoryId);
     ClassRoom findClassRoom = classRoomRepository.findById(classRoomId).orElseThrow(() -> new IllegalArgumentException("해당 교실을 찾을 수가 없습니다."));
     Directory findDirectory = directoryRepository.findById(directoryId).orElseThrow(() -> new IllegalArgumentException("해당 디렉토를을 찾을 수가 없습니다."));
-    System.out.println("통과");
+    log.debug("Directory found successfully");
src/main/java/hello/cluebackend/domain/submission/application/SubmissionCommandService.java (1)

68-81: 단일 조회의 식별 주체 누락: 어떤 사용자의 제출인지 모호함

assignmentId만으로 단일 Submission을 결정할 수 없습니다. 다수 사용자의 제출이 존재할 수 있어 잘못된 사용자 제출을 반환하거나 NPE 위험이 있습니다. userId를 인자로 포함하고 없으면 404를 던지도록 수정하세요.

권장 패치:

-  public SubmissionDto findByAssignmentId(UUID assignmentId) {
+  public SubmissionDto findByAssignmentId(UUID userId, UUID assignmentId) {
     Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId);

-    Submission s = submissionRepository.findByAssignment(assignment);
+    Submission s = submissionRepository.findAllByAssignment(assignment).stream()
+            .filter(sub -> userId.equals(sub.getUser().getUserId()))
+            .findFirst()
+            .orElseThrow(() -> new EntityNotFoundException("해당 사용자의 과제 제출을 찾을 수 없습니다."));

     return SubmissionDto.builder()
src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java (1)

109-121: 파일명 파싱 로직의 인덱스 예외 및 취약한 분리 기준 수정

fullPath.split("_")[1]'_'가 없으면 ArrayIndexOutOfBoundsException로 500이 납니다. 또한 전체 경로에서 단순 split은 취약합니다. 파일명 추출은 Paths.get(...).getFileName() 사용, '_' 미존재 시 안전한 폴백을 적용하세요. 리소스 존재성도 체크해 404를 반환하는 것이 좋습니다.

-            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);
+            String fileName = java.nio.file.Paths.get(fullPath).getFileName().toString();
+            String originalName = fileName.contains("_") ? fileName.substring(fileName.indexOf("_") + 1) : fileName;
+            String encodedFileName = UriUtils.encode(originalName, StandardCharsets.UTF_8);
+            String contentDisposition = "attachment; filename=\"" + encodedFileName + "\"";
+
+            if (!resource.exists() || !resource.isReadable()) {
+                return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
+            }
+            return ResponseEntity.ok()
+                    .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
+                    .body(resource);

추가로 필요한 import:

import java.nio.file.Paths;
src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java (2)

56-66: 교실 멤버십 검증을 먼저 수행한 뒤 목록을 조회하세요

현재는 결과를 가져온 뒤에 인가를 확인합니다. 순서를 반대로 바꾸면 불필요한 쿼리를 줄이고 보안상 안전합니다.

-    List<AssignmentResponseDto> result = assignmentCommandService.findAllById(userId,classId);
-
-    if (!classroomUserService.isUserInClassroom(classId, userId)) {
-        throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다.");
-    }
+    if (!classroomUserService.isUserInClassroom(classId, userId)) {
+      throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다.");
+    }
+    List<AssignmentResponseDto> result = assignmentCommandService.findAllById(userId, classId);

85-105: 첨부파일 다운로드 엔드포인트에 인가 검증이 없습니다

첨부 UUID를 추측할 경우 무단 다운로드 가능성이 있습니다. 수업 멤버십 또는 소유자 검증을 추가하세요.

   public ResponseEntity<Resource> assignmentAttachmentDownload(
           @CurrentUser UUID userId,
           @PathVariable UUID assignmentAttachmentId
   ) throws IOException {
     AssignmentAttachment assignmentAttachment = assignmentCommandService.findAssignmentAttachmentByIdOrderThrow(assignmentAttachmentId);
-    Resource resource = assignmentCommandService.downloadAttachment(assignmentAttachment);
+    UUID classroomId = assignmentAttachment.getAssignment().getClassRoom().getClassRoomId();
+    if (!classroomUserService.isUserInClassroom(classroomId, userId)) {
+      throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다.");
+    }
+    Resource resource = assignmentCommandService.downloadAttachment(assignmentAttachment);

만약 첨부가 제출물(Submission) 소속이라면, 제출물 경로로 통해 클래스룸을 찾아 같은 방식으로 검증하세요.

src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java (1)

6-6: @param 잘못된 패키지 임포트로 인한 런타임 바인딩 실패

현재 io.lettuce.core.dynamic.annotation.Param을 사용하고 있어 Spring Data가 파라미터를 인식하지 못할 수 있습니다. Spring Data용 애노테이션으로 교체하세요.

-import io.lettuce.core.dynamic.annotation.Param;
+import org.springframework.data.repository.query.Param;

해당 변경으로 findClassRoomsByUserId, findUsersByClassRoomId의 네임드 파라미터 바인딩이 정상화됩니다.

Also applies to: 20-21, 23-24

🧹 Nitpick comments (58)
src/main/java/hello/cluebackend/domain/user/presentation/TestController.java (2)

23-23: 하드코딩된 100시간 TTL을 설정값으로 외부화하세요.
운영/개발 환경별로 조정 가능해야 합니다.

예시:

 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.beans.factory.annotation.Value;
 
 @RestController
 @RequiredArgsConstructor
 public class TestController {
 
     private final JWTUtil jwtUtil;
+    @Value("${security.jwt.access-ttl-ms:360000000}") // 기본 100시간
+    private long accessTtlMs;
 
     @PostMapping("/test")
     public ResponseEntity<?> issueToken(@RequestParam UUID userId, @RequestParam String username, @RequestParam String role, HttpServletResponse response) {
-        String access = jwtUtil.createJwt("access", userId, username, role, 100 * 60 * 60 * 1000L);
+        String access = jwtUtil.createJwt("access", userId, username, role, accessTtlMs);

22-22: role 파라미터는 Enum/화이트리스트로 제한하는 것이 안전합니다.
오타/의도치 않은 값 유입 및 권한오용을 줄입니다(dev 전용이라도 습관적으로 제한 권장).

간단히 Bean Validation을 붙이거나, 허용값 체크:

- public ResponseEntity<?> issueToken(@RequestParam UUID userId, @RequestParam String username, @RequestParam String role, HttpServletResponse response) {
+ public ResponseEntity<?> issueToken(@RequestParam UUID userId, @RequestParam String username, @RequestParam String role, HttpServletResponse response) {
+     if (!Set.of("STUDENT","TEACHER","ADMIN").contains(role)) {
+         return ResponseEntity.badRequest().body("invalid role");
+     }

또는 프로젝트의 Role enum이 있다면 @RequestParam Role role로 변경.

src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (1)

19-19: 신규 생성 플로우에서는 ID 세팅을 생략하는 편이 안전합니다

toEntity()에서 항상 documentId를 주입하면, GenerationType.UUID 사용 시 새 엔티티 생성에서 불필요/충돌 위험이 있습니다. 생성/수정 DTO를 분리했거나 null 허용이라면 null일 때는 세팅을 생략하세요.

예시 구현:

public Document toEntity() {
    Document.DocumentBuilder builder = Document.builder()
        .classRoom(classRoom)
        .directory(directory)
        .title(title)
        .type(type)
        .content(content)
        .createdAt(createdAt);

    if (documentId != null) {
        builder.documentId(documentId);
    }
    return builder.build();
}
src/main/java/hello/cluebackend/domain/document/presentation/dto/RequestDocumentDto.java (1)

5-5: 요청 DTO의 UUID 전환 LGTM — API 문서/예제 갱신만 확인

클라이언트가 문자열 UUID를 전송하도록 스웨거/문서의 예제를 갱신해 주세요. 가능하면 @Schema(format = "uuid") 추가도 고려해 주세요.

Also applies to: 14-14

src/main/java/hello/cluebackend/domain/assignment/api/dto/request/CreateAssignmentDto.java (1)

9-9: 요청 스키마의 class_id → UUID 전환 LGTM

역직렬화/검증(@NotNull) 구성은 적절합니다. 문서에 예시 값(예: "class_id": "550e8400-e29b-41d4-a716-446655440000")을 추가하고, OpenAPI에 format: uuid를 명시하면 소비자가 혼동하지 않습니다.

Also applies to: 13-13

src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java (2)

10-10: 불필요한 import 제거 (경고 억제).

GenericGenerator를 사용하지 않습니다. 정리해 주세요.

-import org.hibernate.annotations.GenericGenerator;

29-35: FK 컬럼 네이밍/참조 명시 일관화.

엔티티 PK 컬럼은 class_room_id인데 FK는 classroom_id로 다릅니다. 혼동 방지를 위해 FK도 언더스코어 표기와 일치시키거나, 최소한 referencedColumnName을 명시하는 것을 권장합니다.

-  @JoinColumn(name = "classroom_id")
+  @JoinColumn(name = "class_room_id", referencedColumnName = "class_room_id")
...
-  @JoinColumn(name = "user_id")
+  @JoinColumn(name = "user_id", referencedColumnName = "user_id")
src/main/java/hello/cluebackend/domain/user/presentation/dto/UserDto.java (1)

31-39: createdAt 매핑 제거 또는 엔티티에서 불변화 권장.

toEntity()에서 createdAt을 주입하면 생성 시각이 외부 입력으로 덮일 수 있습니다. 엔티티의 @PrePersist에 맡기고 DTO→엔티티 매핑에서 제외하는 것을 권장합니다.

     public UserEntity toEntity() {
         return UserEntity.builder()
                 .userId(userId)
                 .email(email)
                 .role(role)
                 .username(username)
                 .classCode(classCode)
-                .createdAt(createdAt)
                 .build();
     }
src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java (2)

39-45: createdAt 불변 보장 및 PrePersist 가드.

생성 시각은 변경 불가가 자연스럽습니다. updatable=false와 null일 때만 세팅하도록 가드를 권장합니다.

-    @Column(nullable = false)
+    @Column(nullable = false, updatable = false)
     private LocalDateTime createdAt;

     @PrePersist
     protected void onCreate() {
-        createdAt = LocalDateTime.now();
+        if (createdAt == null) {
+            createdAt = LocalDateTime.now();
+        }
     }

15-17: 민감 필드 Setter 차단.

엔티티 레벨 @SetteruserId, createdAt까지 외부에서 변경 가능하게 만듭니다. 해당 필드에 한해 Setter를 막아 주세요.

 @Builder
 @AllArgsConstructor
 @NoArgsConstructor
 @Table(name = "user_entity")
 public class UserEntity {
     @Id
+    @Setter(AccessLevel.NONE)
     @GeneratedValue(strategy = GenerationType.UUID)
     @Column(name="user_id", nullable = false, updatable = false)
     private UUID userId;
...
-    @Column(nullable = false, updatable = false)
+    @Setter(AccessLevel.NONE)
+    @Column(nullable = false, updatable = false)
     private LocalDateTime createdAt;

Also applies to: 20-24, 39-41

src/main/java/hello/cluebackend/domain/classroom/domain/ClassRoom.java (2)

76-84: NPE/N+1 완화: studentCount 계산 시 null-safe 및 지연로딩 비용 주의.

빌더로 생성된 엔티티에서는 리스트가 null일 수 있고, size()는 컬렉션 초기화를 유발합니다. 간단한 가드 추가를 권장합니다.

-                .studentCount(classRoomUserList.size())
+                .studentCount(classRoomUserList != null ? classRoomUserList.size() : 0)

추가로, 목록 필드에 기본값을 두면 NPE를 줄일 수 있습니다(아래 보조 변경 참고).


43-52: createdAt 불변 보장 및 PrePersist 가드.

createdAt은 생성 후 변경되지 않도록 하는 편이 안전합니다.

-    @Column(nullable = false)
+    @Column(nullable = false, updatable = false)
     private LocalDateTime createdAt;

     @PrePersist
     protected void onCreate() {
-        createdAt = LocalDateTime.now();
+        if (createdAt == null) {
+            createdAt = LocalDateTime.now();
+        }
     }

보조 변경(선택): 컬렉션 기본값과 import 추가

import java.util.ArrayList;
// 파일 내 해당 필드들에 적용
@Builder.Default
@OneToMany(mappedBy = "classRoom", cascade = CascadeType.ALL)
private List<ClassRoomUser> classRoomUserList = new ArrayList<>();

@Builder.Default
@OneToMany(mappedBy = "classRoom", cascade = CascadeType.ALL)
private List<Directory> directoryList = new ArrayList<>();

@Builder.Default
@OneToMany(mappedBy = "classRoom", cascade = CascadeType.ALL)
private List<Document> documentList = new ArrayList<>();
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomDto.java (2)

24-24: Boolean vs boolean 타입 불일치: CardDto와 일관성 맞추기

여기는 Boolean, ClassRoomCardDtoboolean입니다. NPE 위험/직렬화 일관성 측면에서 통일을 권장합니다(가능하면 boolean).

가능한 경우 아래처럼 교체하세요.

-    private Boolean isActivation;
+    private boolean isActivation;

27-38: 코드 생성 매직넘버 상수화

가독성과 재사용을 위해 길이/문자셋을 상수로 뽑는 편이 좋습니다.

+    private static final int CODE_LENGTH = 6;
+    private static final String CODE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
     public void generateCode() {
-        int length = 6;
         StringBuilder randomStringBuilder = new StringBuilder();
         SecureRandom random = new SecureRandom();
-        String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+        String characters = CODE_CHARS;
 
-        for (int i = 0; i < length; i++) {
+        for (int i = 0; i < CODE_LENGTH; i++) {
             int randomIndex = random.nextInt(characters.length());
             randomStringBuilder.append(characters.charAt(randomIndex));
         }
         this.code = randomStringBuilder.toString();
     }
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomCardDto.java (1)

18-18: boolean 필드 네이밍(Double-‘is’ 게터) 혼동 가능성

private boolean isActivation;는 Lombok가 isIsActivation() 게터를 생성해 혼동을 유발할 수 있습니다. 직렬화 명시를 추가하거나 필드명을 active 등으로 교체하는 것을 권장합니다.

다만 API 호환을 유지하려면 다음과 같이 명시해 두는 정도가 안전합니다.

-import lombok.*;
+import lombok.*;
+import com.fasterxml.jackson.annotation.JsonProperty;
 ...
-    private boolean isActivation;
+    @JsonProperty("isActivation")
+    private boolean isActivation;

대안: 필드명을 active로 변경하고 @JsonProperty("isActivation")로 외부 계약만 유지.

src/main/java/hello/cluebackend/domain/document/domain/Document.java (2)

50-59: LAZY 연관을 DTO로 직접 노출 시 직렬화/트랜잭션 경계 이슈 가능

classRoom, directory 엔티티를 DTO로 그대로 넘기면 트랜잭션 경계 밖에서 Lazy 로딩/순환 참조 문제가 생길 수 있습니다. ID만 노출하는 경량 DTO로 매핑하거나, 직렬화 제외 설정을 확인해 주세요.

가능하면 아래처럼 ID만 전달하는 형태를 고려하세요(참고용):

-                .classRoom(classRoom)
-                .directory(directory)
+                // 예: DocumentDto가 *_Id 필드를 가진 경우
+                .classRoomId(classRoom != null ? classRoom.getClassRoomId() : null)
+                .directoryId(directory != null ? directory.getDirectoryId() : null)

21-24: DB 마이그레이션/인덱스 전략 점검(운영 관점)

Long→UUID 전환 시 FK/인덱스/컬럼 타입(BINARY(16) vs VARCHAR(36)) 일관성, 기존 데이터 백필, 다운타임 전략을 포함한 이행 계획을 확인하세요. 대규모 FK 업데이트 시 단계적 마이그레이션(신규 컬럼 추가→백필→FK 재연결→구컬럼 제거) 권장.

src/main/java/hello/cluebackend/domain/assignment/api/dto/response/SubmissionCheck.java (1)

14-14: API 스펙 반영 및 날짜 포맷 일관성 확인

  • submissionId의 UUID 전환에 따라 OpenAPI/프론트(타입: string, 포맷: uuid) 스키마 갱신이 필요합니다.
  • 동일 모듈의 다른 DTO(GetAllAssignmentDto 등)와 날짜 포맷을 맞추는 것이 좋습니다. submittedAt에 @jsonformat 적용을 제안드립니다.

적용 예시:

 package hello.cluebackend.domain.assignment.api.dto.response;

 import lombok.Builder;
 import lombok.Data;

 import java.time.LocalDateTime;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import java.util.UUID;
 ...
   private boolean isSubmitted;
-  private LocalDateTime submittedAt;
+  @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
+  private LocalDateTime submittedAt;
src/main/java/hello/cluebackend/global/common/resolver/CurrentUserArgumentResolver.java (1)

25-25: JWT 클레임 타입 변경에 따른 호환성

기존 Long 기반 토큰과의 호환성 이슈가 있을 수 있습니다. 배포 시 구 토큰 무효화(예: 강제 재로그인, 토큰 블랙리스트 초기화, 키 롤링)를 고려하세요.

src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java (1)

69-76: 리프레시 쿠키 보안 속성 강화 제안

운영 환경에서 setSecure(true)와 SameSite 설정 권장입니다. 현재 setSecure가 주석 처리되어 있어 HTTPS에서도 쿠키가 평문 전송될 수 있습니다.

간단 적용 예:

   private Cookie createCookie(String key, String value) {
     Cookie cookie = new Cookie(key, value);
     cookie.setMaxAge(7 * 24  * 60 * 60);
-    // cookie.setSecure(true);
+    cookie.setSecure(true); // 운영에서 HTTPS 강제 권장
     cookie.setPath("/");
     cookie.setHttpOnly(true);
     return cookie;
   }

SameSite는 ResponseCookie 사용 또는 서버 전역 설정으로 Lax 이상을 권장합니다.

src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (1)

15-15: 타입 전환으로 인한 연쇄 영향 점검

  • 호출부/매퍼/프론트 스키마에서 directoryId를 문자열(UUID)로 취급하는지 확인 부탁드립니다.
  • 읽기 전용 DTO라면 @Setter 제거로 불변성 강화도 고려 가능합니다(선호도에 따라 선택).

선택적 정리 예:

-@Getter
-@Setter
+@Getter
src/main/java/hello/cluebackend/global/security/jwt/RefreshTokenService.java (1)

26-29: 표준 로깅으로 교체

System.out.println 대신 로거 사용으로 레벨 제어 및 운영 로그 일관성 확보 권장.

적용 diff:

-        System.out.println(refreshTokenRepository.existsById(refreshToken));
+        // log at debug level to avoid leaking token state in prod logs
+        // log.debug("refresh token saved? {}", refreshTokenRepository.existsById(refreshToken));

(필요 시 클래스에 Lombok @slf4j 추가)

 @Service
 @Transactional
+@lombok.extern.slf4j.Slf4j
 public class RefreshTokenService {
src/main/java/hello/cluebackend/domain/user/presentation/dto/CustomOAuth2User.java (1)

30-37: 권한 컬렉션 단순화

익명 클래스 대신 SimpleGrantedAuthority 사용으로 가독성 향상.

적용 diff:

-        collection.add(new GrantedAuthority() {
-            @Override
-            public String getAuthority() {
-                return userDTO.getRole().name();
-            }
-        });
+        collection.add(new org.springframework.security.core.authority.SimpleGrantedAuthority(
+            userDTO.getRole().name()
+        ));
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (1)

23-30: 메서드 명명 혼동: toDto → toEntity 권장

DTO에서 엔티티로 변환하므로 이름을 toEntity()로 바꾸면 의도가 명확합니다. (선택)

적용 diff:

-    public Directory toDto() {
+    public Directory toEntity() {
         return Directory.builder()
src/main/java/hello/cluebackend/domain/user/service/UserService.java (1)

18-26: 트랜잭션 경계 지정 제안

쓰기(registration)는 @transactional, 조회는 readOnly 적용을 권장합니다. JPA 컨텍스트 일관성과 성능에 유리합니다.

적용 diff:

+import org.springframework.transaction.annotation.Transactional;
@@
-    public void registerUser(DefaultRegisterUserDto userDTO) {
+    @Transactional
+    public void registerUser(DefaultRegisterUserDto userDTO) {
         UserEntity userEntity = new UserEntity(

추신: OAuth2 기반으로 비밀번호 저장이 불필요하다는 팀 합의를 이전 PR에서 확인했습니다(잘 준수됨).

src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionRepository.java (1)

15-15: 단건 조회 시 Optional 사용 또는 유일성 보장 검토

findByAssignment(Assignment assignment)가 null 반환 가능성이 있어 NPE 리스크가 있습니다. 도메인에서 과제-제출이 1:N 관계라면 단건 메서드는 모호합니다. 아래 중 하나를 권장합니다.

  • 단건이 보장됨: DB에 유니크 제약 추가 후 Optional<Submission>로 변경
  • 다건 가능: List<Submission>로 통일
-  Submission findByAssignment(Assignment assignment);
+  java.util.Optional<Submission> findByAssignment(Assignment assignment);

추가로 다건이라면:

-  Submission findByAssignment(Assignment assignment);
+  java.util.List<Submission> findByAssignment(Assignment assignment);
src/main/java/hello/cluebackend/domain/directory/domain/repository/DirectoryRepository.java (1)

9-11: @repository 어노테이션은 선택 사항

Spring Data JPA에서는 인터페이스에 @Repository가 없어도 컴포넌트 스캔과 프록시 생성이 됩니다. 팀 컨벤션에 맞춰 제거해도 됩니다. 기능 영향은 없습니다.

-@Repository
-public interface DirectoryRepository extends JpaRepository<Directory, UUID> {
+public interface DirectoryRepository extends JpaRepository<Directory, UUID> {
src/main/java/hello/cluebackend/domain/document/domain/repository/DocumentRepository.java (1)

9-11: @repository 어노테이션 제거 고려(선택)

동일하게 인터페이스에 @Repository는 필수 아님. 컨벤션에 맞추어 단순화 가능.

-@Repository
-public interface DocumentRepository extends JpaRepository<Document, UUID> {
+public interface DocumentRepository extends JpaRepository<Document, UUID> {
src/main/java/hello/cluebackend/domain/user/domain/repository/UserRepository.java (1)

11-15: Long userId 참조 잔존 여부 점검 완료
src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java 39번째 줄의 주석 처리된 Long userId = jwtUtil.getUserId(token) 외에는 코드 내에 Long userId 사용 흔적이 없습니다. 해당 주석 코드는 제거를 권장합니다.

src/main/java/hello/cluebackend/domain/classroomuser/application/ClassroomUserService.java (1)

32-37: 엔티티 로딩 없이 존재 여부 체크로 최적화

exists 체크에 엔티티 변환(toEntity)까지 필요 없습니다. 리포지토리에 ID 기반 exists 메서드를 추가해 N+1/불필요 로딩을 줄이세요.

-  public boolean isUserInClassroom(UUID classRoomId, UUID userId) {
-    ClassRoom classRoom = classRoomService.findById(classRoomId).toEntity();
-    UserEntity user = userService.findById(userId).toEntity();
-    return classRoomUserRepository.existsByClassRoomAndUser(classRoom,user);
-  }
+  public boolean isUserInClassroom(UUID classRoomId, UUID userId) {
+    return classRoomUserRepository
+        .existsByClassRoom_ClassRoomIdAndUser_UserId(classRoomId, userId);
+  }

리포지토리 메서드 예:

boolean existsByClassRoom_ClassRoomIdAndUser_UserId(UUID classRoomId, UUID userId);

추가로, 파라미터 @nonnull(또는 명시적 null 체크) 적용을 권장합니다.

src/main/java/hello/cluebackend/domain/assignment/api/dto/response/GetAllAssignmentDto.java (1)

13-19: DTO의 UUID 전환 LGTM

필드/생성자 모두 UUID로 일관화되었습니다. 응답 전용 DTO라면 불변성 강화를 위해 record로의 전환도 고려해볼 수 있습니다(선택).

record 예시:

public record GetAllAssignmentDto(
  UUID assignmentId,
  String title,
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime startDate,
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime endDate
) {}
src/main/java/hello/cluebackend/domain/classroom/domain/repository/ClassRoomRepository.java (2)

13-13: existsBy... 반환 타입은 primitive 사용 권장

오토박싱 불필요합니다. primitive로 변경해 미세 성능/가독성 개선하세요.

-    Boolean existsByCode(String code);
+    boolean existsByCode(String code);

13-15: code 컬럼에 DB 유니크 제약 권장

애플리케이션 레벨 existsByCode/findByCode만으로는 경쟁 상태를 막기 어렵습니다. DB에 유니크 인덱스를 추가해 무결성을 보장하세요.

src/main/java/hello/cluebackend/domain/directory/domain/Directory.java (3)

34-35: 자식 컬렉션 삭제 일관성: orphanRemoval=true 권장

부모에서 자식 제거 시 고아 레코드 방지합니다.

-    @OneToMany(mappedBy = "directory", cascade = CascadeType.ALL)
+    @OneToMany(mappedBy = "directory", cascade = CascadeType.ALL, orphanRemoval = true)

12-18: 테이블 이름 단수 통일 목표를 코드에 명시화

네이밍 전략에 의존하지 않고 @Table로 단수명을 고정하세요.

-@Entity
+@Entity
+@Table(name = "directory")
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
 @Getter
 @Setter

37-44: DTO에 엔티티 직접 노출 지양

toDto()에서 classRoom 엔티티를 그대로 넣으면 LAZY 로딩/순환 참조 리스크가 있습니다. classRoomId 등 식별자만 전달하는 경량 DTO를 고려하세요.

src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentRepository.java (1)

15-20: 중복 방지 위해 DISTINCT 권장

조인 결과에 따라 동일 과제가 중복될 수 있습니다. 안전하게 DISTINCT를 사용하세요.

-  @Query("SELECT a FROM Submission s" +
+  @Query("SELECT DISTINCT a FROM Submission s" +
           " join s.assignment a" +
           " where s.user.userId =:userId" +
           " AND s.isSubmitted = false")
   List<Assignment> getAllByUser(@Param("userId") UUID userId);
src/main/java/hello/cluebackend/domain/submission/domain/Submission.java (2)

31-32: DB 제약 명시: is_submitted NOT NULL로 고정.

프리미티브 boolean이므로 DB도 nullable=false로 명시해 두는 편이 안전합니다.

-  @Column(name = "is_submitted")
+  @Column(name = "is_submitted", nullable = false)
   private boolean isSubmitted;

50-53: cancel 시 제출시각은 null이 직관적입니다.

취소 시점으로 덮어쓰면 “제출됨” 같은 의미로 오해될 수 있습니다. null로 비우는 쪽이 조회·통계 로직에 유리합니다.

   public void cancel() {
     this.isSubmitted = false;
-    this.submittedAt = LocalDateTime.now();
+    this.submittedAt = null;
   }
src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java (1)

6-7: 미사용 import 제거.

org.springframework.core.io.Resource는 사용되지 않습니다.

-import org.springframework.core.io.Resource;
src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java (1)

40-47: 코드 정리 - 주석 처리된 코드 제거

Line 43의 주석 처리된 필터 조건이 남아있습니다. 사용하지 않는다면 제거하는 것이 좋습니다.

 public List<ClassRoomCardDto> findMyClassRoomById(UUID userId) {
     List<ClassRoomUser> classRoomUsers = classRoomUserRepository.findByUser_UserId(userId);
     return classRoomUsers.stream()
-//                .filter(cu -> cu.getUser().getRole() == Role.STUDENT)
             .map(ClassRoomUser::getClassRoom)
             .map(ClassRoom::toCardDTO)
             .toList();
 }
src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java (1)

37-43: 주석 처리된 코드 제거

Line 39-40에 사용하지 않는 주석이 남아있습니다. 필요하지 않다면 제거하는 것이 좋습니다.

 public ResponseEntity<?> getAllInfo(HttpServletRequest request, @PathVariable UUID classId){
     String token = jwtUtil.getToken(request);
-//        Long userId = jwtUtil.getUserId(token);
-//        Role role = jwtUtil.getRole(token);

     return ResponseEntity.ok(classRoomService.getAllInfo(classId));
 }
src/main/java/hello/cluebackend/domain/submission/application/SubmissionCommandService.java (4)

33-48: in-memory 필터 대신 DB로 위임 권장 (불필요 스캔 감소)

현재 전체 제출을 메모리에서 필터링합니다. 가능하면 리포지토리에 findAllByAssignmentAndUserId(assignment, userId) 쿼리를 추가해 DB 레벨에서 필터링하세요.

가능 시 아래처럼 단순화할 수 있습니다:

-    List<Submission> submissions = submissionRepository.findAllByAssignment(assignment);
-    return submissions.stream()
-            .filter(s -> s.getUser().getUserId().equals(userId))
+    List<Submission> submissions = submissionRepository.findAllByAssignmentAndUserId(assignment, userId)
+            ;
     return submissions.stream()
             .map(s -> SubmissionCheck.builder()

50-65: 메서드 인자 userId 미사용

userId가 전달되나 사용되지 않습니다. 의도적으로 “과제별 전체 제출”을 반환하는 메서드라면 시그니처에서 userId를 제거하거나, 사용자별 조회가 목적이면 필터에 사용하세요.

가능한 정리안:

-  public List<SubmissionDto> findAllByAssignmentId(UUID userId, UUID assignmentId) {
+  public List<SubmissionDto> findAllByAssignmentId(UUID assignmentId) {

93-105: 메서드 명 오해 소지: 첨부 조회인데 findAllAssignment

반환이 SubmissionAttachmentDto 리스트이므로 findAllAttachments 등으로 명확히 해주세요. 기존 시그니처 호환이 필요하면 deprecated 위임 메서드를 남기는 방식을 권장합니다.

권장 리네이밍:

-  public List<SubmissionAttachmentDto> findAllAssignment(UUID submissionId) {
+  public List<SubmissionAttachmentDto> findAllAttachments(UUID submissionId) {

호환용 위임(추가 코드):

@Deprecated
public List<SubmissionAttachmentDto> findAllAssignment(UUID submissionId) {
  return findAllAttachments(submissionId);
}

107-110: 메서드 명 오타: findsubmission...findSubmission...

카멜케이스가 깨져 IDE 검색/자동완성에 불리합니다. 예외 메시지 띄어쓰기 역시 수정 권장.

-  public SubmissionAttachment findsubmissionAttachmentByIdOrThrow(UUID submissionAttachmentId) {
-    return submissionAttachmentRepository.findById(submissionAttachmentId)
-            .orElseThrow(() -> new EntityNotFoundException("해당 과제 제출 첨부파일을 찾을수 없습니다."));
-  }
+  public SubmissionAttachment findSubmissionAttachmentByIdOrThrow(UUID submissionAttachmentId) {
+    return submissionAttachmentRepository.findById(submissionAttachmentId)
+            .orElseThrow(() -> new EntityNotFoundException("해당 과제 제출 첨부파일을 찾을 수 없습니다."));
+  }

호환용 위임(추가 코드):

@Deprecated
public SubmissionAttachment findsubmissionAttachmentByIdOrThrow(UUID submissionAttachmentId) {
  return findSubmissionAttachmentByIdOrThrow(submissionAttachmentId);
}
src/main/java/hello/cluebackend/domain/assignment/application/AssignmentCommandService.java (3)

33-56: LGTM: 단건 조회 DTO 구성

첨부 조회 후 DTO 매핑이 명확합니다. 성능이 민감하다면 첨부를 batch fetch(예: @BatchSize)로 최적화 가능.


59-65: 파라미터 미사용 및 N+1 패턴 가능성

  • userId 미사용입니다. 필요 없으면 제거하거나 권한/가시성 필터에 활용하세요.
  • 각 과제마다 findById(...) 재호출은 N+1 가능성이 있습니다. 스트림에서 직접 DTO를 만들거나 조인/페치 전략으로 줄이는 것을 고려하세요.

제안:

-  public List<AssignmentResponseDto> findAllById(UUID userId, UUID classId) {
+  public List<AssignmentResponseDto> findAllByClassId(UUID classId) {

73-76: 메서드 명 오타: OrderThrowOrThrow

오탈자 정정 권장. 메시지 띄어쓰기 (“찾을수”→“찾을 수”)도 함께 수정해주세요.

-  public AssignmentAttachment findAssignmentAttachmentByIdOrderThrow(UUID attachmentId){
+  public AssignmentAttachment findAssignmentAttachmentByIdOrThrow(UUID attachmentId){
     return assignmentAttachmentRepository.findById(attachmentId)
-            .orElseThrow(() -> new EntityNotFoundException("해당 첨부 파일을 찾을수 없습니다."));
+            .orElseThrow(() -> new EntityNotFoundException("해당 첨부 파일을 찾을 수 없습니다."));
   }
src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java (5)

31-39: 생성 응답은 201 Created + Location 헤더 권장

리소스 생성 시 REST 관례에 따라 201과 Location을 반환하면 클라이언트 UX가 좋아집니다.

아래처럼 조정 가능 (추가 import 필요: java.net.URI):

-    return ResponseEntity.ok(assignment.getAssignmentId());
+    return ResponseEntity.created(URI.create("/api/assignments/" + assignment.getAssignmentId()))
+            .body(assignment.getAssignmentId());

53-60: 변수명 의미 명확화 (assignmentassignmentIdResult)

반환값이 UUID이므로 변수명을 명확히 해 가독성을 개선하세요.

-    UUID assignment = assignmentQueryService.patchAssignment(assignmentId, assignmentDto);
-
-    return ResponseEntity.ok(assignment);
+    UUID assignmentIdResult = assignmentQueryService.patchAssignment(assignmentId, assignmentDto);
+    return ResponseEntity.ok(assignmentIdResult);

65-75: 파일 업로드 입력 검증 추가 권장

빈 배열/NULL 업로드에 대한 400 처리 및 최대 개수/사이즈 제한 검증을 추가하세요.

예시 추가:

if (files == null || files.length == 0) {
  return ResponseEntity.badRequest().body("업로드할 파일이 없습니다.");
}

80-86: 링크 업로드 입력 검증

빈 리스트/NULL에 대한 400 응답을 권장합니다.

예시:

if (assignmentAttachmentDto == null || assignmentAttachmentDto.isEmpty()) {
  return ResponseEntity.badRequest().body("업로드할 링크가 없습니다.");
}

91-96: 응답 메시지 부정확 (“과제” → “첨부파일”) 및 권한 검증 확인

  • 실제 동작은 첨부 삭제입니다. 메시지를 교정하세요.
  • 첨부 삭제 권한(작성자/담당자) 검증 위치 확인 필요.
-    return ResponseEntity.ok("과제를 성공적으로 삭제했습니다.");
+    return ResponseEntity.ok("첨부파일을 성공적으로 삭제했습니다.");
src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java (2)

46-48: UUID RequestPart 바인딩 확인 및 단순 스칼라 값은 @RequestParam 권장

멀티파트 요청에서 UUID 같은 스칼라 값은 @RequestParam이 바인딩/클라이언트 구현 면에서 더 단순합니다. 현재 @RequestPart를 사용하면 클라이언트가 파트 타입/인코딩을 맞춰 보내야 해서 400이 나올 여지가 있습니다. 변경 시 API 소비자와의 계약을 반드시 확인해 주세요.

다음과 같이 변경을 제안합니다:

-            @RequestPart("classRoomId") UUID classRoomId,
-            @RequestPart("directoryId") UUID directoryId,
+            @RequestParam("classRoomId") UUID classRoomId,
+            @RequestParam("directoryId") UUID directoryId,
-            @RequestPart("classRoomId") UUID classRoomId,
-            @RequestPart("directoryId") UUID directoryId,
+            @RequestParam("classRoomId") UUID classRoomId,
+            @RequestParam("directoryId") UUID directoryId,

Also applies to: 70-72


56-59: System.out.println 제거하고 로거 사용

운영 코드에서 System.out.println은 피해주세요. 로거로 대체하면 수준/출력 제어가 용이합니다.

-            System.out.println("requestDocumentDto = " + requestDocumentDto);
+            log.debug("requestDocumentDto={}", requestDocumentDto);
src/main/java/hello/cluebackend/domain/submission/api/SubmissionQueryController.java (1)

27-29: UUID 마이그레이션 적용은 적절함. userId 활용 여부는 정리 필요

UUID로의 시그니처 변경은 일관되고 적절합니다. 다만 @CurrentUser UUID userId가 일부 메서드에서 사용되지 않습니다. 권한/소유자 검증에 쓸 계획이 없다면 제거하고, 사용할 계획이면 Service 계층으로 전달해 검증을 완료하세요.

Also applies to: 37-39, 47-49, 57-59

src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java (1)

34-34: TODO 주석 마무리 및 애노테이션 기반 인가 적용

주석 끝에 불필요한 따옴표가 있고, 전역적으로 애노테이션 기반 인가(@PreAuthorize, 커스텀 @CheckClassroomMember) 도입을 권장합니다. 엔드포인트별 중복 로직을 줄일 수 있습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ff3ce49 and f249a3c.

⛔ Files ignored due to path filters (9)
  • src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignment.java is excluded by !**/generated/**
  • src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignmentAttachment.java is excluded by !**/generated/**
  • src/main/generated/hello/cluebackend/domain/classroom/domain/QClassRoom.java is excluded by !**/generated/**
  • src/main/generated/hello/cluebackend/domain/classroomuser/domain/QClassRoomUser.java is excluded by !**/generated/**
  • src/main/generated/hello/cluebackend/domain/directory/domain/QDirectory.java is excluded by !**/generated/**
  • src/main/generated/hello/cluebackend/domain/document/domain/QDocument.java is excluded by !**/generated/**
  • src/main/generated/hello/cluebackend/domain/submission/domain/QSubmission.java is excluded by !**/generated/**
  • src/main/generated/hello/cluebackend/domain/submission/domain/QSubmissionAttachment.java is excluded by !**/generated/**
  • src/main/generated/hello/cluebackend/domain/user/domain/QUserEntity.java is excluded by !**/generated/**
📒 Files selected for processing (56)
  • src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java (4 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java (5 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/api/dto/request/CreateAssignmentDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentResponseDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/api/dto/response/GetAllAssignmentDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/api/dto/response/SubmissionCheck.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/application/AssignmentCommandService.java (4 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/application/AssignmentQueryService.java (5 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java (2 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentAttachmentRepository.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentRepository.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/classroom/domain/ClassRoom.java (2 hunks)
  • src/main/java/hello/cluebackend/domain/classroom/domain/repository/ClassRoomRepository.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java (6 hunks)
  • src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java (2 hunks)
  • src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomCardDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java (6 hunks)
  • src/main/java/hello/cluebackend/domain/classroomuser/application/ClassroomUserService.java (2 hunks)
  • src/main/java/hello/cluebackend/domain/classroomuser/domain/ClassRoomUser.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/directory/domain/Directory.java (2 hunks)
  • src/main/java/hello/cluebackend/domain/directory/domain/repository/DirectoryRepository.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/directory/presentation/dto/RequestDirectoryDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/directory/service/DirectoryService.java (2 hunks)
  • src/main/java/hello/cluebackend/domain/document/domain/Document.java (2 hunks)
  • src/main/java/hello/cluebackend/domain/document/domain/repository/DocumentRepository.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java (4 hunks)
  • src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/document/presentation/dto/RequestDocumentDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/document/service/DocumentService.java (3 hunks)
  • src/main/java/hello/cluebackend/domain/submission/api/SubmissionCommandController.java (5 hunks)
  • src/main/java/hello/cluebackend/domain/submission/api/SubmissionQueryController.java (6 hunks)
  • src/main/java/hello/cluebackend/domain/submission/application/SubmissionCommandService.java (6 hunks)
  • src/main/java/hello/cluebackend/domain/submission/application/SubmissionQueryService.java (6 hunks)
  • src/main/java/hello/cluebackend/domain/submission/domain/Submission.java (2 hunks)
  • src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionAttachmentRepository.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionRepository.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java (2 hunks)
  • src/main/java/hello/cluebackend/domain/user/domain/repository/UserRepository.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/user/presentation/TestController.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/user/presentation/dto/CustomOAuth2User.java (2 hunks)
  • src/main/java/hello/cluebackend/domain/user/presentation/dto/UserDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/user/service/UserService.java (2 hunks)
  • src/main/java/hello/cluebackend/global/common/resolver/CurrentUserArgumentResolver.java (2 hunks)
  • src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java (2 hunks)
  • src/main/java/hello/cluebackend/global/config/JWTFilter.java (2 hunks)
  • src/main/java/hello/cluebackend/global/config/JWTUtil.java (3 hunks)
  • src/main/java/hello/cluebackend/global/security/jwt/RefreshTokenService.java (2 hunks)
  • src/main/resources/application-prod.yml (1 hunks)
  • src/test/java/hello/cluebackend/domain/classroom/presentation/ClassRoomControllerTest.java (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-08T23:38:29.680Z
Learnt from: fixgramwork
PR: EscapeFrame/clueBackend#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.

Applied to files:

  • src/main/java/hello/cluebackend/domain/user/service/UserService.java
📚 Learning: 2025-08-26T14:23:15.065Z
Learnt from: Hgyeol
PR: EscapeFrame/clueBackend#48
File: src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java:3-5
Timestamp: 2025-08-26T14:23:15.065Z
Learning: AssignmentAttachmentDto has two different versions: request package contains only String url for URL uploads, while response package contains full metadata (fileType, value, originalFileName, contentType, size) for responses. The controller and service correctly use request DTO for uploads and response DTO for queries.

Applied to files:

  • src/main/java/hello/cluebackend/domain/submission/application/SubmissionQueryService.java
  • src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java
📚 Learning: 2025-07-13T16:08:21.631Z
Learnt from: Hgyeol
PR: EscapeFrame/clueBackend#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.

Applied to files:

  • src/main/java/hello/cluebackend/domain/user/presentation/TestController.java
  • src/test/java/hello/cluebackend/domain/classroom/presentation/ClassRoomControllerTest.java
  • src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java
  • src/main/java/hello/cluebackend/global/config/JWTFilter.java
🧬 Code graph analysis (24)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (4)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java (1)
  • Getter (9-21)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/RequestDirectoryDto.java (1)
  • Getter (7-13)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java (1)
  • Getter (8-17)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (1)
  • Builder (12-31)
src/main/java/hello/cluebackend/domain/assignment/api/dto/response/SubmissionCheck.java (3)
src/main/java/hello/cluebackend/domain/assignment/api/dto/response/GetAllAssignmentDto.java (1)
  • Data (10-24)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (1)
  • Builder (12-31)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (1)
  • Builder (14-38)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomCardDto.java (4)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java (1)
  • Getter (9-21)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomDto.java (1)
  • Getter (13-52)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (1)
  • Getter (9-19)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/RequestDirectoryDto.java (1)
  • Getter (7-13)
src/main/java/hello/cluebackend/domain/document/presentation/dto/RequestDocumentDto.java (3)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (1)
  • Getter (9-19)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java (1)
  • Getter (8-17)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (1)
  • Builder (14-38)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/RequestDirectoryDto.java (4)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java (1)
  • Getter (9-21)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (1)
  • Getter (9-19)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java (1)
  • Getter (8-17)
src/main/java/hello/cluebackend/domain/document/presentation/dto/RequestDocumentDto.java (1)
  • Getter (7-18)
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 (9-19)
src/main/java/hello/cluebackend/domain/document/presentation/dto/RequestDocumentDto.java (1)
  • Getter (7-18)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (1)
  • Builder (14-38)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (2)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (1)
  • Getter (9-19)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/RequestDirectoryDto.java (1)
  • Getter (7-13)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (3)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (1)
  • Builder (12-31)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java (1)
  • Getter (8-17)
src/main/java/hello/cluebackend/domain/document/presentation/dto/RequestDocumentDto.java (1)
  • Getter (7-18)
src/main/java/hello/cluebackend/domain/user/presentation/dto/CustomOAuth2User.java (1)
src/main/java/hello/cluebackend/domain/user/service/CustomOAuth2UserService.java (3)
  • CustomOAuth2UserService (16-70)
  • loadUser (25-68)
  • CustomOAuth2UserService (21-23)
src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java (3)
src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java (1)
  • Entity (17-74)
src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java (1)
  • Entity (10-40)
src/main/java/hello/cluebackend/domain/assignment/domain/BaseEntity.java (1)
  • EntityListeners (15-32)
src/main/java/hello/cluebackend/domain/directory/service/DirectoryService.java (1)
src/main/java/hello/cluebackend/domain/directory/presentation/DirectoryController.java (2)
  • deleteDirectory (58-72)
  • DirectoryController (15-73)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomDto.java (3)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java (1)
  • Getter (9-21)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomCardDto.java (1)
  • Getter (7-19)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/RequestDirectoryDto.java (1)
  • Getter (7-13)
src/main/java/hello/cluebackend/global/common/resolver/CurrentUserArgumentResolver.java (1)
src/main/java/hello/cluebackend/global/common/annotation/CurrentUser.java (1)
  • Target (8-11)
src/main/java/hello/cluebackend/domain/classroomuser/domain/ClassRoomUser.java (1)
src/main/java/hello/cluebackend/domain/classroom/domain/ClassRoom.java (1)
  • Entity (15-86)
src/main/java/hello/cluebackend/domain/assignment/api/dto/response/GetAllAssignmentDto.java (3)
src/main/java/hello/cluebackend/domain/assignment/api/dto/response/SubmissionCheck.java (1)
  • Data (9-17)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (1)
  • Builder (12-31)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (1)
  • Builder (14-38)
src/main/java/hello/cluebackend/domain/user/presentation/TestController.java (4)
src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java (1)
  • RestController (29-106)
src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java (1)
  • RestController (21-97)
src/main/java/hello/cluebackend/domain/submission/api/SubmissionCommandController.java (1)
  • RestController (25-84)
src/main/java/hello/cluebackend/domain/submission/api/SubmissionQueryController.java (1)
  • RestController (17-75)
src/main/java/hello/cluebackend/global/security/jwt/RefreshTokenService.java (3)
src/main/java/hello/cluebackend/global/security/jwt/RefreshToken.java (2)
  • RefreshToken (8-25)
  • RefreshToken (23-24)
src/main/java/hello/cluebackend/global/security/jwt/RefreshTokenRepository.java (1)
  • RefreshTokenRepository (6-9)
src/main/java/hello/cluebackend/global/security/jwt/AuthController.java (2)
  • AuthController (11-29)
  • refreshToken (20-28)
src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentResponseDto.java (2)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (1)
  • Builder (12-31)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (1)
  • Builder (14-38)
src/main/java/hello/cluebackend/domain/user/presentation/dto/UserDto.java (6)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java (1)
  • Getter (9-21)
src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomCardDto.java (1)
  • Getter (7-19)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java (1)
  • Getter (9-19)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java (1)
  • Getter (8-17)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (1)
  • Builder (12-31)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (1)
  • Builder (14-38)
src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java (1)
src/main/java/hello/cluebackend/domain/assignment/domain/BaseEntity.java (1)
  • EntityListeners (15-32)
src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java (4)
src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java (1)
  • Entity (17-74)
src/main/java/hello/cluebackend/domain/submission/domain/Submission.java (1)
  • Entity (11-65)
src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java (1)
  • Entity (8-37)
src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionAttachmentDto.java (1)
  • Builder (6-13)
src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java (1)
src/main/java/hello/cluebackend/domain/document/presentation/dto/FileUpload.java (1)
  • FileUpload (8-20)
src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java (1)
src/main/java/hello/cluebackend/domain/assignment/domain/BaseEntity.java (1)
  • EntityListeners (15-32)
src/main/java/hello/cluebackend/domain/assignment/api/dto/request/CreateAssignmentDto.java (2)
src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryDto.java (1)
  • Builder (12-31)
src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentDto.java (1)
  • Builder (14-38)

Comment on lines +44 to 50
@CurrentUser UUID userId,
@PathVariable UUID assignmentId
) {
AssignmentResponseDto result = assignmentCommandService.findById(assignmentId);
Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId);
Long classroomId = assignment.getClassRoom().getClassRoomId();
UUID classroomId = assignment.getClassRoom().getClassRoomId();
if (!classroomUserService.isUserInClassroom(classroomId, userId)) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

인가(권한) 검증을 데이터 조회보다 먼저 수행하세요

DTO 조회를 먼저 수행하면 불필요한 DB 접근 및 정보 노출 가능성이 있습니다. 아래처럼 순서를 바꾸세요.

-    AssignmentResponseDto result = assignmentCommandService.findById(assignmentId);
-    Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId);
+    Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId);
     UUID classroomId = assignment.getClassRoom().getClassRoomId();
     if (!classroomUserService.isUserInClassroom(classroomId, userId)) {
       throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다.");
     }
+    AssignmentResponseDto result = assignmentCommandService.findById(assignmentId);
📝 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.

Suggested change
@CurrentUser UUID userId,
@PathVariable UUID assignmentId
) {
AssignmentResponseDto result = assignmentCommandService.findById(assignmentId);
Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId);
Long classroomId = assignment.getClassRoom().getClassRoomId();
UUID classroomId = assignment.getClassRoom().getClassRoomId();
if (!classroomUserService.isUserInClassroom(classroomId, userId)) {
Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId);
UUID classroomId = assignment.getClassRoom().getClassRoomId();
if (!classroomUserService.isUserInClassroom(classroomId, userId)) {
throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다.");
}
AssignmentResponseDto result = assignmentCommandService.findById(assignmentId);
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java
around lines 44 to 50, the controller currently fetches the DTO before
performing authorization which can cause unnecessary DB access and data
exposure; change the flow to first retrieve the Assignment entity (use
findByIdOrThrow), perform the classroom membership check with
classroomUserService.isUserInClassroom using
assignment.getClassRoom().getClassRoomId(), and only after the check pass either
convert that same Assignment to AssignmentResponseDto or call findById for the
DTO if conversion is nontrivial—this avoids extra DB calls and ensures
authorization is enforced before data exposure.

Comment on lines 78 to 83
// 첨부 파일 혹은 링크 전체 조회 (선생, 학생)
@GetMapping("/{submissionId}/attachment")
public ResponseEntity<List<SubmissionAttachmentDto>> findAllAssignments(@CurrentUser Long userId, @PathVariable Long submissionId) {
public ResponseEntity<List<SubmissionAttachmentDto>> findAllAssignments(@CurrentUser UUID userId, @PathVariable UUID submissionId) {
List<SubmissionAttachmentDto> result = submissionCommandService.findAllAssignment(submissionId);
return ResponseEntity.ok(result);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

제출물 첨부 조회 엔드포인트에 인가 검증이 누락되어 있습니다

현재 사용자 검증 없이 제출물 첨부 목록을 노출합니다. 수업 멤버 또는 적법한 당사자(담당 교사/제출자)만 접근 가능하도록 확인 후 조회하세요.

예시 수정안:

   @GetMapping("/{submissionId}/attachment")
-  public ResponseEntity<List<SubmissionAttachmentDto>> findAllAssignments(@CurrentUser UUID userId, @PathVariable UUID submissionId) {
-    List<SubmissionAttachmentDto> result = submissionCommandService.findAllAssignment(submissionId);
+  public ResponseEntity<List<SubmissionAttachmentDto>> findAllAssignments(@CurrentUser UUID userId,
+                                                                          @PathVariable UUID submissionId) {
+    var submission = submissionCommandService.findByIdOrThrow(submissionId);
+    UUID classroomId = submission.getAssignment().getClassRoom().getClassRoomId();
+    if (!classroomUserService.isUserInClassroom(classroomId, userId)) {
+      throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다.");
+    }
+    List<SubmissionAttachmentDto> result = submissionCommandService.findAllAssignment(submissionId);
     return ResponseEntity.ok(result);
   }

추가 import 필요:

import hello.cluebackend.domain.submission.domain.Submission;

또한 메서드명 findAllAssignments는 반환 타입(SubmissionAttachmentDto)에 비해 혼동됩니다. findAllSubmissionAttachments 등으로의 변경을 권장합니다.

Comment on lines +44 to 46
@CurrentUser UUID userId,
@PathVariable UUID assignmentId){
assignmentQueryService.delete(assignmentId);
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

@CurrentUser 미사용 — 권한 검증 위치 확인 필요

컨트롤러/서비스/AOP 중 어디서 권한을 검증하는지 확인해주세요. 미검증 시 삭제 권한 취약점이 됩니다.

권한 검증 흔적 점검 스크립트:


🏁 Script executed:

#!/bin/bash
# 컨트롤러/서비스의 메서드 보안 적용 여부 점검
rg -nP '@PreAuthorize|@Secured|hasRole|hasAuthority' -C2 src/main/java
# 삭제/수정 서비스 진입점 확인
rg -nP 'delete\s*\(|patchAssignment\s*\(' -C2 src/main/java
# CurrentUser 리졸버와 Security 설정 점검
rg -nP 'CurrentUserArgumentResolver|SecurityConfig|MethodSecurity' -n -C2 src/main/java

Length of output: 9223


삭제 권한 검증 누락: AssignmentQueryController.delete에서 @CurrentUser로 받은 userId와 삭제 대상 과제의 소유주를 비교해 권한을 검증하세요.

🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java
around lines 44-46, add an ownership check using the @CurrentUser UUID userId:
before calling assignmentQueryService.delete(assignmentId), load the assignment
(or its owner id) via the service/repository, compare that owner id to userId,
and if they differ throw an appropriate 403 exception (e.g.,
ResponseStatusException with HttpStatus.FORBIDDEN or a custom
AccessDeniedException); only call delete when the ids match. Ensure the service
call you use is transactional/read-only and returns a clear owner id or
Optional<Assignment> so you can handle not-found vs forbidden cases distinctly.


import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

주의: prod에서 ddl-auto=update 적용 여부 확인 필요

PR 서머리에 따르면 prod 프로필이 validate → update로 바뀐 것으로 보입니다. 운영에서 update는 예상치 못한 스키마 변경/데이터 리스크가 큽니다. 마이그레이션은 Flyway/Liquibase로 관리하고 prod는 validate로 유지하세요.


🏁 Script executed:

#!/bin/bash
# prod 프로필에서 ddl-auto 설정 확인
fd -t f 'application-*.yml' | xargs -I{} sh -c "echo '--- {}' && sed -n '1,200p' {} | rg -n 'ddl-auto\s*:\s*(update|create|create-drop)' -n"
fd -t f 'application-*.properties' | xargs -I{} sh -c "echo '--- {}' && rg -n 'spring\.jpa\.hibernate\.ddl-auto\s*=\s*(update|create|create-drop)' {}"

Length of output: 329


🏁 Script executed:

#!/bin/bash
# 전체 application 설정 파일에서 ddl-auto 설정 검색
rg -n --glob 'application*.yml' 'ddl-auto'
rg -n --glob 'application*.properties' 'spring\.jpa\.hibernate\.ddl-auto'

Length of output: 458


prod 프로파일의 ddl-auto를 validate로 되돌리세요
application-prod.yml(5행)에서 ddl-auto: update로 설정된 것을 확인했습니다. 운영 DB 스키마 변경 위험을 방지하려면 prod는 validate로 유지하고, 스키마 변경은 Flyway/Liquibase로 관리하세요.

🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentResponseDto.java
around line 7, the review notes that application-prod.yml currently sets
spring.jpa.hibernate.ddl-auto: update (line 5); change this to ddl-auto:
validate in the production profile to prevent automatic schema changes at
runtime, and ensure any schema migrations are applied via Flyway or Liquibase
instead.

Comment on lines +88 to 89
public void uploadFileAttachment(UUID assignmentId, MultipartFile file) {
Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

DB 변경 메서드에 @transactional 누락

uploadFileAttachment는 파일 저장 후 엔티티를 저장합니다. 다른 메서드와 일관되게 트랜잭션을 명시해 주세요.

-  // 첨부 파일 추가
-  public void uploadFileAttachment(UUID assignmentId, MultipartFile file) {
+  // 첨부 파일 추가
+  @Transactional
+  public void uploadFileAttachment(UUID assignmentId, MultipartFile 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.

Suggested change
public void uploadFileAttachment(UUID assignmentId, MultipartFile file) {
Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId);
// 첨부 파일 추가
@Transactional
public void uploadFileAttachment(UUID assignmentId, MultipartFile file) {
Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId);
// … existing implementation …
}
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/assignment/application/AssignmentQueryService.java
around lines 88-89, the uploadFileAttachment method performs file storage and
then saves an entity but is missing the @Transactional annotation; add the same
transactional annotation used by other mutating methods in this service (e.g.,
@Transactional with default propagation, or the project-standard transaction
attributes) to the method declaration so the entity save is executed within a
transaction and will roll back on failure.

Comment on lines +28 to 31
public UserDto findById(UUID userId) {
UserEntity userEntity = userRepository.findById(userId).get();
return userEntity.toUserDTO();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optional.get() 직호출로 인한 NoSuchElementException 위험

미존재 사용자 id에 대해 런타임 예외가 발생할 수 있습니다. 도메인 친화적 예외로 전환하세요.

적용 diff:

+import jakarta.persistence.EntityNotFoundException;
+import org.springframework.transaction.annotation.Transactional;
@@
-    public UserDto findById(UUID userId) {
-        UserEntity userEntity = userRepository.findById(userId).get();
-        return userEntity.toUserDTO();
-    }
+    @Transactional(readOnly = true)
+    public UserDto findById(UUID userId) {
+        UserEntity userEntity = userRepository.findById(userId)
+                .orElseThrow(() -> new EntityNotFoundException("User not found: " + userId));
+        return userEntity.toUserDTO();
+    }
📝 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.

Suggested change
public UserDto findById(UUID userId) {
UserEntity userEntity = userRepository.findById(userId).get();
return userEntity.toUserDTO();
}
// ── at the top of src/main/java/hello/cluebackend/domain/user/service/UserService.java ──
import jakarta.persistence.EntityNotFoundException;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// ── replace the original method (lines 28–31) with the following ──
@Transactional(readOnly = true)
public UserDto findById(UUID userId) {
- UserEntity userEntity = userRepository.findById(userId).get();
UserEntity userEntity = userRepository.findById(userId)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + userId));
return userEntity.toUserDTO();
}
// …other methods…
}
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/user/service/UserService.java around
lines 28 to 31, the direct call to Optional.get() can throw
NoSuchElementException for missing user IDs; replace the get() with orElseThrow
and throw a domain-friendly exception (e.g., UserNotFoundException or a custom
NotFoundException) including the userId in the message, so the method becomes:
retrieve via userRepository.findById(userId).orElseThrow(() -> new
UserNotFoundException(userId)), then return userEntity.toUserDTO(); ensure the
new exception class exists (or reuse a suitable existing one) and is unchecked
or handled by the service/controller layer.

String username = jwtUtil.getUsername(accessToken);
Role role = jwtUtil.getRole(accessToken);
Long userId = jwtUtil.getUserId(accessToken);
UUID userId = jwtUtil.getUserId(accessToken);
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Long 기반 구 토큰과의 호환성/마이그레이션 전략 필요

배포 시 기존 JWT에 Long userId가 담겨 있으면 UUID 파싱 실패로 전량 401이 됩니다. 강제 재로그인 공지, 토큰 무효화, 또는 구/신 포맷 이중 처리(기간 한정) 중 하나를 결정/문서화하세요.

구/신 토큰 생성·파싱 지점을 점검하세요.


🏁 Script executed:

#!/bin/bash
rg -nP 'createJwt|getUserId\s*\(' -C3 src/main/java
rg -nP 'claim\\s*\\(.*userId|userId\\)' -n -C2 src/main/java
rg -nP '\bLong\b.*userId|\bUUID\b.*userId' -n -C2 src/main/java

Length of output: 43052


Long 기반 JWT 호환성: 마이그레이션 전략 필요

배포 전 기존 JWT(Long userId) 파싱 오류(현재 UUID.fromString)로 인한 401 발생 문제를 다음 중 하나로 해결·문서화하세요

  • 강제 재로그인 공지 및 토큰 무효화
  • 구/신 포맷 이중 처리(기간 한정)

src/main/java/hello/cluebackend/global/security/jwt/RefreshTokenService.java,
src/main/java/hello/cluebackend/global/config/JWTFilter.java,
src/main/java/hello/cluebackend/global/config/JWTUtil.java 의 JWT 생성·파싱 로직을 검토해 구·신 토큰 모두 정상 처리되도록 구현하세요.

🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/global/config/JWTFilter.java around line 57
(and review related code in
src/main/java/hello/cluebackend/global/security/jwt/RefreshTokenService.java and
src/main/java/hello/cluebackend/global/config/JWTUtil.java), the current JWT
parsing assumes userId is a UUID (UUID.fromString) which will fail for legacy
tokens that contain a Long; implement dual-format handling: attempt to parse
userId as UUID first, if that fails catch the exception and parse as Long (or as
numeric string) and convert to a canonical UUID mapping or wrap it in a
migration path (e.g., create a deterministic UUID from the Long or store
legacyLong value on the Authentication principal), update JWT generation/parsing
in JWTUtil and refresh token handling to emit a new-format UUID token when users
re-authenticate, and add a short-lived compatibility branch or feature-flag to
accept both formats (or alternatively document/implement forced re-login and
token invalidation) so old tokens do not produce 401s during the migration
window.

Comment on lines +26 to 29
public UUID getUserId(String token) {
String uuid = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("userId", String.class);
return UUID.fromString(uuid);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

UUID 변환 시 예외 처리 필요

UUID.fromString() 호출 시 잘못된 형식의 문자열이 전달되면 IllegalArgumentException이 발생합니다. JWT 토큰이 손상되거나 이전 Long 타입 ID를 포함한 토큰이 있을 경우 애플리케이션이 중단될 수 있습니다.

다음과 같이 예외 처리를 추가하세요:

 public UUID getUserId(String token) {
     String uuid = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("userId", String.class);
-    return UUID.fromString(uuid);
+    try {
+        return UUID.fromString(uuid);
+    } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException("Invalid UUID format in JWT token", e);
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public UUID getUserId(String token) {
String uuid = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("userId", String.class);
return UUID.fromString(uuid);
}
public UUID getUserId(String token) {
String uuid = Jwts.parser().verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload()
.get("userId", String.class);
try {
return UUID.fromString(uuid);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid UUID format in JWT token", e);
}
}
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/global/config/JWTUtil.java around lines 26 to
29, UUID.fromString(tokenPayload) can throw IllegalArgumentException for
malformed IDs; wrap the conversion in a try-catch that catches
IllegalArgumentException, log the failure with the token/userId payload (or
minimal context), and rethrow a clear runtime Jwt-related exception (e.g.,
io.jsonwebtoken.JwtException or a custom runtime) with the original exception as
the cause so the caller can handle invalid/migrated tokens without the app
crashing.

jpa:
hibernate:
ddl-auto: validate
ddl-auto: update
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

프로덕션에서 ddl-auto: update 사용은 고위험 — 즉시 되돌리고 명시적 마이그레이션으로 전환 권장

UUID로 PK/FK 타입을 변경하는 대규모 스키마 전환에서 Hibernate의 자동 업데이트는 컬럼 타입 변경(bigint → uuid/char/varchar), FK 재작성, 인덱스/시퀀스 정리 등을 안정적으로 처리하지 못해 스키마 드리프트·데이터 손실·부팅 실패를 유발할 수 있습니다. 특히 RDBMS/Dialect별로 타입 변경은 update가 지원/보장되지 않습니다.

권장 조치(요약):

  • prod에서는 ddl-autovalidate(또는 none)로 전환하고, Flyway/Liquibase로 명시적 마이그레이션을 적용하세요.
  • 마이그레이션 스크립트에 PK/모든 FK·조인테이블·인덱스 재생성, 데이터 캐스팅, 시퀀스/트리거 정리, 기본값/제약조건 재부여를 포함하세요.
  • 실행 전 전체 백업, 스테이징(프로드 스냅샷) 리허설, 블루-그린/점진 배포 또는 유지보수 창 확보.

제안 diff:

-      ddl-auto: update
+      ddl-auto: validate

검증 스크립트:


🏁 Script executed:

#!/bin/bash
# prod에서 update가 남아있는지와 마이그레이션 도구 사용 여부 점검

echo "== ddl-auto 설정 위치 확인 =="
rg -nP '\bddl-auto\s*:\s*(\w+)' -C2

echo -e "\n== Flyway/Liquibase 사용 흔적 확인 =="
fd -H -a '(db/migration|flyway|liquibase|changelog)' 2>/dev/null || true
rg -nP 'spring\.(flyway|liquibase)\.' -C2 || true

echo -e "\n== 프로필별 설정 스캔(application*.yml/properties) =="
fd -a 'application*.yml' | xargs -I{} sh -c 'echo "--- {} ---"; rg -n "ddl-auto" "{}" || true'
fd -a 'application*.properties' | xargs -I{} sh -c 'echo "--- {} ---"; rg -n "ddl-auto" "{}" || true'

Length of output: 1383


prod 프로파일에서 ddl-auto를 validate로 전환하고 명시적 마이그레이션 도입

-      ddl-auto: update
+      ddl-auto: validate

파일: src/main/resources/application-prod.yml (5행)
프로덕션 환경에서 ddl-auto: update는 대규모 스키마 변경 시 스키마 드리프트·데이터 손실·부팅 실패를 유발할 수 있습니다. 즉시 validate(또는 none)로 변경하고 Flyway/Liquibase 기반의 마이그레이션 스크립트로 컬럼 타입 변경, FK·인덱스 재생성, 데이터 캐스팅, 제약조건 재부여 등을 명시적으로 관리하세요.

📝 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.

Suggested change
ddl-auto: update
ddl-auto: validate
🤖 Prompt for AI Agents
In src/main/resources/application-prod.yml around line 5, change the Hibernate
ddl-auto setting from "update" to "validate" (or "none") for the production
profile and remove automatic schema mutation; instead add and configure a
database migration tool (Flyway or Liquibase) in your project, create explicit
versioned migration scripts for any schema changes (column type changes,
FK/index re-creation, data casts, constraint reapplication), and ensure
application startup runs migrations separately from the app (or fails fast on
validation) so schema changes are applied via migrations rather than Hibernate
auto-update.

// given
String token = "fake-token";
Long userId = 1L;
UUID userId = 1L;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

UUID 초기화 타입 불일치로 인한 컴파일 에러

UUID userId = 1L;는 컴파일되지 않습니다. 아래처럼 수정하세요.

-        UUID userId = 1L;
+        UUID userId = UUID.randomUUID();
📝 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.

Suggested change
UUID userId = 1L;
UUID userId = UUID.randomUUID();
🤖 Prompt for AI Agents
In
src/test/java/hello/cluebackend/domain/classroom/presentation/ClassRoomControllerTest.java
around line 38, the code assigns a long to a UUID (UUID userId = 1L;) which
causes a compile error; replace that assignment with a valid UUID creation such
as UUID userId = UUID.fromString("00000000-0000-0000-0000-000000000001") or UUID
userId = UUID.randomUUID() (or construct via new UUID(msb, lsb) if a specific
value is required) so the variable is a proper java.util.UUID.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/main/resources/application-dev.yml (2)

13-13: 루트 로거 debug는 과도합니다: 패키지/SQL 로거로 범위 제한 권장

root: debug는 3rd-party까지 전부 상세 로그가 출력되어 노이즈와 민감정보(토큰, 식별자) 노출 위험이 있습니다. root는 info로 낮추고, 우리 패키지나 필요한 로거만 선택적으로 올리는 편이 안전합니다.

 logging:
   level:
-    root: debug
+    root: info
+    hello.cluebackend: debug
+    # 필요 시 SQL만 확인
+    org.hibernate.SQL: debug

5-5: dev 프로필 ddl-auto를 validate→update로, prod 프로필 ddl-auto를 update→validate로 전환
src/main/resources/application-dev.yml

-      ddl-auto: validate
+      ddl-auto: update

src/main/resources/application-prod.yml

-      ddl-auto: update
+      ddl-auto: validate

장기적으로는 Flyway/Liquibase 기반 마이그레이션(db/migration 등) 관리 후 CI/CD에서 적용을 권장합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f249a3c and 3a2df9b.

📒 Files selected for processing (1)
  • src/main/resources/application-dev.yml (1 hunks)

Copy link
Member

@Hgyeol Hgyeol left a comment

Choose a reason for hiding this comment

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

uuid로 바꿔서 보안성이 더 높아졌네요!

@Hgyeol Hgyeol merged commit 252006c into develop Sep 3, 2025
1 check passed
@github-project-automation github-project-automation bot moved this from 🛠 In Progress to ✅ Done in clue_ver_1 Sep 3, 2025
@coderabbitai coderabbitai bot mentioned this pull request Sep 3, 2025
@coderabbitai coderabbitai bot mentioned this pull request Sep 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

개발 개발과 관련된 이슈 버그 기능이 정상적으로 작동하지 않을 떄 이슈

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

entity 값 불일치

2 participants