Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class QAssignment extends EntityPathBase<Assignment> {

public final QBaseEntity _super = new QBaseEntity(this);

public final NumberPath<Long> assignmentId = createNumber("assignmentId", Long.class);
public final ComparablePath<java.util.UUID> assignmentId = createComparable("assignmentId", java.util.UUID.class);

public final hello.cluebackend.domain.classroom.domain.QClassRoom classRoom;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class QAssignmentAttachment extends EntityPathBase<AssignmentAttachment>

public final QAssignment assignment;

public final NumberPath<Long> assignmentAttachmentId = createNumber("assignmentAttachmentId", Long.class);
public final ComparablePath<java.util.UUID> assignmentAttachmentId = createComparable("assignmentAttachmentId", java.util.UUID.class);

public final StringPath contentType = createString("contentType");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class QClassRoom extends EntityPathBase<ClassRoom> {

public static final QClassRoom classRoom = new QClassRoom("classRoom");

public final NumberPath<Long> classRoomId = createNumber("classRoomId", Long.class);
public final ComparablePath<java.util.UUID> classRoomId = createComparable("classRoomId", java.util.UUID.class);

public final ListPath<hello.cluebackend.domain.classroomuser.domain.ClassRoomUser, hello.cluebackend.domain.classroomuser.domain.QClassRoomUser> classRoomUserList = this.<hello.cluebackend.domain.classroomuser.domain.ClassRoomUser, hello.cluebackend.domain.classroomuser.domain.QClassRoomUser>createList("classRoomUserList", hello.cluebackend.domain.classroomuser.domain.ClassRoomUser.class, hello.cluebackend.domain.classroomuser.domain.QClassRoomUser.class, PathInits.DIRECT2);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class QClassRoomUser extends EntityPathBase<ClassRoomUser> {

public final hello.cluebackend.domain.classroom.domain.QClassRoom classRoom;

public final NumberPath<Long> classRoomUserId = createNumber("classRoomUserId", Long.class);
public final ComparablePath<java.util.UUID> classRoomUserId = createComparable("classRoomUserId", java.util.UUID.class);

public final hello.cluebackend.domain.user.domain.QUserEntity user;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class QDirectory extends EntityPathBase<Directory> {

public final hello.cluebackend.domain.classroom.domain.QClassRoom classRoom;

public final NumberPath<Long> directoryId = createNumber("directoryId", Long.class);
public final ComparablePath<java.util.UUID> directoryId = createComparable("directoryId", java.util.UUID.class);

public final NumberPath<Integer> directoryOrder = createNumber("directoryOrder", Integer.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class QDocument extends EntityPathBase<Document> {

public final hello.cluebackend.domain.directory.domain.QDirectory directory;

public final NumberPath<Long> documentId = createNumber("documentId", Long.class);
public final ComparablePath<java.util.UUID> documentId = createComparable("documentId", java.util.UUID.class);

public final StringPath title = createString("title");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class QSubmission extends EntityPathBase<Submission> {
//inherited
public final StringPath lastModifiedBy = _super.lastModifiedBy;

public final NumberPath<Long> submissionId = createNumber("submissionId", Long.class);
public final ComparablePath<java.util.UUID> submissionId = createComparable("submissionId", java.util.UUID.class);

public final DateTimePath<java.time.LocalDateTime> submittedAt = createDateTime("submittedAt", java.time.LocalDateTime.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class QSubmissionAttachment extends EntityPathBase<SubmissionAttachment>

public final QSubmission submission;

public final NumberPath<Long> SubmissionAttachmentId = createNumber("SubmissionAttachmentId", Long.class);
public final ComparablePath<java.util.UUID> SubmissionAttachmentId = createComparable("SubmissionAttachmentId", java.util.UUID.class);

public final EnumPath<fileType> type = createEnum("type", fileType.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class QUserEntity extends EntityPathBase<UserEntity> {

public final EnumPath<Role> role = createEnum("role", Role.class);

public final NumberPath<Long> userId = createNumber("userId", Long.class);
public final ComparablePath<java.util.UUID> userId = createComparable("userId", java.util.UUID.class);

public final StringPath username = createString("username");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/assignments")
Expand All @@ -40,12 +41,12 @@ public class AssignmentCommandController {
// 과제 단일 조회
@GetMapping("/{assignmentId}")
public ResponseEntity<AssignmentResponseDto> getAssignment(
@CurrentUser Long userId,
@PathVariable Long assignmentId
@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)) {
Comment on lines +44 to 50
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.

throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다.");
}
Expand All @@ -55,8 +56,8 @@ public ResponseEntity<AssignmentResponseDto> getAssignment(
// 교실 과제 전체 조회
@GetMapping("/{classId}/all")
public ResponseEntity<List<AssignmentResponseDto>> getAllClassroomAssignment(
@CurrentUser Long userId,
@PathVariable Long classId
@CurrentUser UUID userId,
@PathVariable UUID classId
) {
List<AssignmentResponseDto> result = assignmentCommandService.findAllById(userId,classId);

Expand All @@ -69,23 +70,23 @@ public ResponseEntity<List<AssignmentResponseDto>> getAllClassroomAssignment(

// 메인 페이지 모든 과제 조회
@GetMapping("/me")
public ResponseEntity<List<GetAllAssignmentDto>> getAllAssignments(@CurrentUser Long userId) {
public ResponseEntity<List<GetAllAssignmentDto>> getAllAssignments(@CurrentUser UUID userId) {
List<GetAllAssignmentDto> result = assignmentCommandService.findAllAssignmentMe(userId);
return ResponseEntity.ok(result);
}

// 첨부 파일 혹은 링크 전체 조회 (선생, 학생)
@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);
}
Comment on lines 78 to 83
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 등으로의 변경을 권장합니다.


// 첨부 파일 다운로드
@GetMapping("/{assignmentAttachmentId}/download")
public ResponseEntity<Resource> assignmentAttachmentDownload(
@CurrentUser Long userId,
@PathVariable Long assignmentAttachmentId
@CurrentUser UUID userId,
@PathVariable UUID assignmentAttachmentId
) throws IOException {
AssignmentAttachment assignmentAttachment = assignmentCommandService.findAssignmentAttachmentByIdOrderThrow(assignmentAttachmentId);
Resource resource = assignmentCommandService.downloadAttachment(assignmentAttachment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.io.IOException;
import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/assignments")
Expand All @@ -27,8 +28,8 @@ public class AssignmentQueryController {

// 과제 생성
@PostMapping
public ResponseEntity<Long> createAssignment(
@CurrentUser Long userId,
public ResponseEntity<UUID> createAssignment(
@CurrentUser UUID userId,
@Valid @RequestBody CreateAssignmentDto request
) {
Assignment assignment = assignmentQueryService.save(userId, request);
Expand All @@ -40,29 +41,29 @@ public ResponseEntity<Long> createAssignment(
// 과제 삭제
@DeleteMapping("/{assignmentId}")
public ResponseEntity<?> deleteAssignment(
@CurrentUser Long userId,
@PathVariable Long assignmentId){
@CurrentUser UUID userId,
@PathVariable UUID assignmentId){
assignmentQueryService.delete(assignmentId);
Comment on lines +44 to 46
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.

return ResponseEntity.ok("과제를 성공적으로 삭제했습니다.");
}

// 과제 수정
@PatchMapping("/{assignmentId}")
public ResponseEntity<?> modifyAssignment(
@CurrentUser Long userId,
@PathVariable Long assignmentId,
@CurrentUser UUID userId,
@PathVariable UUID assignmentId,
@Valid @RequestBody ModifyAssignmentDto assignmentDto
) {
Long assignment = assignmentQueryService.patchAssignment(assignmentId, assignmentDto);
UUID assignment = assignmentQueryService.patchAssignment(assignmentId, assignmentDto);

return ResponseEntity.ok(assignment);
}

// 첨부 파일 추가
@PostMapping("/{assignmentId}/file")
public ResponseEntity<?> uploadAttachments(
@CurrentUser Long userId,
@PathVariable Long assignmentId,
@CurrentUser UUID userId,
@PathVariable UUID assignmentId,
@RequestParam("files") MultipartFile[] files
) throws IOException {

Expand All @@ -76,8 +77,8 @@ public ResponseEntity<?> uploadAttachments(
// 첨부 링크 추가
@PostMapping("/{assignmentId}/link")
public ResponseEntity<?> urlAttachments(
@CurrentUser Long userId,
@PathVariable Long assignmentId,
@CurrentUser UUID userId,
@PathVariable UUID assignmentId,
@RequestBody List<AssignmentAttachmentDto> assignmentAttachmentDto
){
assignmentQueryService.uploadUrlAttachment(assignmentId, assignmentAttachmentDto);
Expand All @@ -87,8 +88,8 @@ public ResponseEntity<?> urlAttachments(
// 첨부파일 혹은 링크 삭제
@DeleteMapping("/attachment/{attachmentId}")
public ResponseEntity<?> deleteAttachment(
@CurrentUser Long userId,
@PathVariable Long attachmentId
@CurrentUser UUID userId,
@PathVariable UUID attachmentId
) {
assignmentQueryService.deleteAttachment(attachmentId);
return ResponseEntity.ok("과제를 성공적으로 삭제했습니다.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
import lombok.Builder;

import java.time.LocalDateTime;
import java.util.UUID;

@Builder
public record CreateAssignmentDto (
@NotNull @JsonProperty("class_id") Long classId,
@NotNull @JsonProperty("class_id") UUID classId,
@NotNull String title,
@NotNull String content,
@NotNull @JsonProperty("start_date") @JsonFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime startDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

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.


@Builder
public record AssignmentResponseDto(
Long assignmentId,
UUID assignmentId,
String title,
String content,
LocalDateTime startDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
import lombok.Data;

import java.time.LocalDateTime;
import java.util.UUID;

@Data
@Builder
public class GetAllAssignmentDto {
private Long assignmentId; // 과제 아이디
private UUID assignmentId; // 과제 아이디
private String title; // 과제 제목
@JsonFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime startDate; // 과제 시작일
@JsonFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime endDate; // 마감일

public GetAllAssignmentDto(Long assignmentId, String title, LocalDateTime startDate, LocalDateTime endDate){
public GetAllAssignmentDto(UUID assignmentId, String title, LocalDateTime startDate, LocalDateTime endDate){
this.assignmentId = assignmentId;
this.title = title;
this.startDate = startDate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import lombok.Data;

import java.time.LocalDateTime;
import java.util.UUID;

@Data
@Builder
public class SubmissionCheck {
private String userName;
private int classNumberGrade;
private Long submissionId;
private UUID submissionId;
private boolean isSubmitted;
private LocalDateTime submittedAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.io.IOException;
import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
Expand All @@ -29,7 +30,7 @@ public class AssignmentCommandService{
private final FileService fileService;

// 과제 단일 조회
public AssignmentResponseDto findById(Long assignmentId) {
public AssignmentResponseDto findById(UUID assignmentId) {
Assignment a = findByIdOrThrow(assignmentId);
List<AssignmentAttachment> assignmentAttachments = assignmentAttachmentRepository.findAllByAssignment(a);

Expand All @@ -55,7 +56,7 @@ public AssignmentResponseDto findById(Long assignmentId) {
}

// 과제 전체 조회
public List<AssignmentResponseDto> findAllById(Long userId, Long classId) {
public List<AssignmentResponseDto> findAllById(UUID userId, UUID classId) {
ClassRoom classRoom = classRoomService.findById(classId).toEntity();
List<Assignment> assignments = assignmentRepository.findAllByClassRoom(classRoom);
return assignments.stream()
Expand All @@ -64,18 +65,18 @@ public List<AssignmentResponseDto> findAllById(Long userId, Long classId) {
}

// 과제 ID를 통한 조회
public Assignment findByIdOrThrow(Long assignmentId) {
public Assignment findByIdOrThrow(UUID assignmentId) {
return assignmentRepository.findById(assignmentId)
.orElseThrow(() -> new EntityNotFoundException("해당 과제를 찾을수 없습니다."));
}

public AssignmentAttachment findAssignmentAttachmentByIdOrderThrow(Long attachmentId){
public AssignmentAttachment findAssignmentAttachmentByIdOrderThrow(UUID attachmentId){
return assignmentAttachmentRepository.findById(attachmentId)
.orElseThrow(() -> new EntityNotFoundException("해당 첨부 파일을 찾을수 없습니다."));
}

// 사용자가 속한 모든 수업 과제 조회
public List<GetAllAssignmentDto> findAllAssignmentMe(Long userId) {
public List<GetAllAssignmentDto> findAllAssignmentMe(UUID userId) {
List<Assignment> assignments = assignmentRepository.getAllByUser(userId);
return assignments.stream()
.map(a -> GetAllAssignmentDto.builder()
Expand Down
Loading