diff --git a/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignment.java b/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignment.java index 704859f0..9ece2c25 100644 --- a/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignment.java +++ b/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignment.java @@ -22,28 +22,14 @@ public class QAssignment extends EntityPathBase { public static final QAssignment assignment = new QAssignment("assignment"); - public final QBaseEntity _super = new QBaseEntity(this); - public final ComparablePath assignmentId = createComparable("assignmentId", java.util.UUID.class); public final hello.cluebackend.domain.classroom.domain.QClassRoom classRoom; public final StringPath content = createString("content"); - //inherited - public final StringPath createdBy = _super.createdBy; - - //inherited - public final DateTimePath createdDate = _super.createdDate; - public final DateTimePath endDate = createDateTime("endDate", java.time.LocalDateTime.class); - //inherited - public final DateTimePath lastModified = _super.lastModified; - - //inherited - public final StringPath lastModifiedBy = _super.lastModifiedBy; - public final DateTimePath startDate = createDateTime("startDate", java.time.LocalDateTime.class); public final ListPath submissions = this.createList("submissions", hello.cluebackend.domain.submission.domain.Submission.class, hello.cluebackend.domain.submission.domain.QSubmission.class, PathInits.DIRECT2); diff --git a/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignmentAttachment.java b/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignmentAttachment.java index 1725f340..14650615 100644 --- a/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignmentAttachment.java +++ b/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignmentAttachment.java @@ -22,26 +22,12 @@ public class QAssignmentAttachment extends EntityPathBase public static final QAssignmentAttachment assignmentAttachment = new QAssignmentAttachment("assignmentAttachment"); - public final QBaseEntity _super = new QBaseEntity(this); - public final QAssignment assignment; public final ComparablePath assignmentAttachmentId = createComparable("assignmentAttachmentId", java.util.UUID.class); public final StringPath contentType = createString("contentType"); - //inherited - public final StringPath createdBy = _super.createdBy; - - //inherited - public final DateTimePath createdDate = _super.createdDate; - - //inherited - public final DateTimePath lastModified = _super.lastModified; - - //inherited - public final StringPath lastModifiedBy = _super.lastModifiedBy; - public final StringPath originalFileName = createString("originalFileName"); public final NumberPath size = createNumber("size", Long.class); diff --git a/src/main/generated/hello/cluebackend/domain/assignment/domain/QBaseEntity.java b/src/main/generated/hello/cluebackend/domain/assignment/domain/QBaseEntity.java deleted file mode 100644 index 3562f97a..00000000 --- a/src/main/generated/hello/cluebackend/domain/assignment/domain/QBaseEntity.java +++ /dev/null @@ -1,43 +0,0 @@ -package hello.cluebackend.domain.assignment.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QBaseEntity is a Querydsl query type for BaseEntity - */ -@Generated("com.querydsl.codegen.DefaultSupertypeSerializer") -public class QBaseEntity extends EntityPathBase { - - private static final long serialVersionUID = 1570896856L; - - public static final QBaseEntity baseEntity = new QBaseEntity("baseEntity"); - - public final StringPath createdBy = createString("createdBy"); - - public final DateTimePath createdDate = createDateTime("createdDate", java.time.LocalDateTime.class); - - public final DateTimePath lastModified = createDateTime("lastModified", java.time.LocalDateTime.class); - - public final StringPath lastModifiedBy = createString("lastModifiedBy"); - - public QBaseEntity(String variable) { - super(BaseEntity.class, forVariable(variable)); - } - - public QBaseEntity(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QBaseEntity(PathMetadata metadata) { - super(BaseEntity.class, metadata); - } - -} - diff --git a/src/main/generated/hello/cluebackend/domain/submission/domain/QBaseEntity.java b/src/main/generated/hello/cluebackend/domain/submission/domain/QBaseEntity.java deleted file mode 100644 index ddd8f67c..00000000 --- a/src/main/generated/hello/cluebackend/domain/submission/domain/QBaseEntity.java +++ /dev/null @@ -1,43 +0,0 @@ -package hello.cluebackend.domain.submission.domain; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QBaseEntity is a Querydsl query type for BaseEntity - */ -@Generated("com.querydsl.codegen.DefaultSupertypeSerializer") -public class QBaseEntity extends EntityPathBase { - - private static final long serialVersionUID = -1972490601L; - - public static final QBaseEntity baseEntity = new QBaseEntity("baseEntity"); - - public final StringPath createdBy = createString("createdBy"); - - public final DateTimePath createdDate = createDateTime("createdDate", java.time.LocalDateTime.class); - - public final DateTimePath lastModified = createDateTime("lastModified", java.time.LocalDateTime.class); - - public final StringPath lastModifiedBy = createString("lastModifiedBy"); - - public QBaseEntity(String variable) { - super(BaseEntity.class, forVariable(variable)); - } - - public QBaseEntity(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QBaseEntity(PathMetadata metadata) { - super(BaseEntity.class, metadata); - } - -} - diff --git a/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmission.java b/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmission.java index e0ee7516..655cc00c 100644 --- a/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmission.java +++ b/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmission.java @@ -22,24 +22,12 @@ public class QSubmission extends EntityPathBase { public static final QSubmission submission = new QSubmission("submission"); - public final QBaseEntity _super = new QBaseEntity(this); - public final hello.cluebackend.domain.assignment.domain.QAssignment assignment; - //inherited - public final StringPath createdBy = _super.createdBy; - - //inherited - public final DateTimePath createdDate = _super.createdDate; + public final hello.cluebackend.domain.classroom.domain.QClassRoom classRoom; public final BooleanPath isSubmitted = createBoolean("isSubmitted"); - //inherited - public final DateTimePath lastModified = _super.lastModified; - - //inherited - public final StringPath lastModifiedBy = _super.lastModifiedBy; - public final ComparablePath submissionId = createComparable("submissionId", java.util.UUID.class); public final DateTimePath submittedAt = createDateTime("submittedAt", java.time.LocalDateTime.class); @@ -65,6 +53,7 @@ public QSubmission(PathMetadata metadata, PathInits inits) { public QSubmission(Class type, PathMetadata metadata, PathInits inits) { super(type, metadata, inits); this.assignment = inits.isInitialized("assignment") ? new hello.cluebackend.domain.assignment.domain.QAssignment(forProperty("assignment"), inits.get("assignment")) : null; + this.classRoom = inits.isInitialized("classRoom") ? new hello.cluebackend.domain.classroom.domain.QClassRoom(forProperty("classRoom")) : null; this.user = inits.isInitialized("user") ? new hello.cluebackend.domain.user.domain.QUserEntity(forProperty("user")) : null; } diff --git a/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmissionAttachment.java b/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmissionAttachment.java index e679044c..40359565 100644 --- a/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmissionAttachment.java +++ b/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmissionAttachment.java @@ -32,7 +32,7 @@ public class QSubmissionAttachment extends EntityPathBase public final ComparablePath SubmissionAttachmentId = createComparable("SubmissionAttachmentId", java.util.UUID.class); - public final EnumPath type = createEnum("type", fileType.class); + public final EnumPath type = createEnum("type", FileType.class); public final hello.cluebackend.domain.user.domain.QUserEntity user; diff --git a/src/main/java/hello/cluebackend/ClueBackendApplication.java b/src/main/java/hello/cluebackend/ClueBackendApplication.java index e8f60d48..7dae8826 100644 --- a/src/main/java/hello/cluebackend/ClueBackendApplication.java +++ b/src/main/java/hello/cluebackend/ClueBackendApplication.java @@ -16,8 +16,8 @@ public static void main(String[] args) { SpringApplication.run(ClueBackendApplication.class, args); } - @Bean - public AuditorAware auditorProvider(){ - return () -> Optional.of(UUID.randomUUID().toString()); - } +// @Bean +// public AuditorAware auditorProvider(){ +// return () -> Optional.of(UUID.randomUUID().toString()); +// } baseEntity의 필요성을 느끼지 못해서 잠시 정지 } \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/AssignmentAttachmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/AssignmentAttachmentDto.java deleted file mode 100644 index 3645818e..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/AssignmentAttachmentDto.java +++ /dev/null @@ -1,5 +0,0 @@ -package hello.cluebackend.domain.assignment.api.dto.request; - -public record AssignmentAttachmentDto( - String url -) {} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentAttachmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentAttachmentDto.java deleted file mode 100644 index c7ce9506..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentAttachmentDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package hello.cluebackend.domain.assignment.api.dto.response; - -import hello.cluebackend.domain.assignment.domain.FileType; -import lombok.Builder; - -@Builder -public record AssignmentAttachmentDto( - FileType type, - String value, - String originalFileName, - String contentType, - Long size -) {} diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentResponseDto.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentResponseDto.java deleted file mode 100644 index 04d5c489..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentResponseDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package hello.cluebackend.domain.assignment.api.dto.response; - -import lombok.Builder; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.UUID; - -@Builder -public record AssignmentResponseDto( - UUID assignmentId, - String title, - String content, - LocalDateTime startDate, - LocalDateTime endDate, - String userName, - - // 과제 첨부 데이터 - List xAssignmentResponseDtos -) { } \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/GetAllAssignmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/GetAllAssignmentDto.java deleted file mode 100644 index 4bd11a8d..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/GetAllAssignmentDto.java +++ /dev/null @@ -1,24 +0,0 @@ -package hello.cluebackend.domain.assignment.api.dto.response; - -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Builder; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.UUID; - -@Data -@Builder -public class GetAllAssignmentDto { - 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(UUID assignmentId, String title, LocalDateTime startDate, LocalDateTime endDate){ - this.assignmentId = assignmentId; - this.title = title; - this.startDate = startDate; - this.endDate = endDate; - } -} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/SubmissionCheck.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/SubmissionCheck.java deleted file mode 100644 index 164df56f..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/SubmissionCheck.java +++ /dev/null @@ -1,17 +0,0 @@ -package hello.cluebackend.domain.assignment.api.dto.response; - -import lombok.Builder; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.UUID; - -@Data -@Builder -public class SubmissionCheck { - private String userName; - private int classNumberGrade; - private UUID submissionId; - private boolean isSubmitted; - private LocalDateTime submittedAt; -} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentCommandService.java b/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentCommandService.java index 48d441ea..f056f3a1 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentCommandService.java +++ b/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentCommandService.java @@ -1,8 +1,7 @@ package hello.cluebackend.domain.assignment.application; -import hello.cluebackend.domain.assignment.api.dto.response.AssignmentAttachmentDto; -import hello.cluebackend.domain.assignment.api.dto.response.AssignmentResponseDto; -import hello.cluebackend.domain.assignment.api.dto.response.GetAllAssignmentDto; +import hello.cluebackend.domain.assignment.presentation.dto.response.AssignmentResponseDto; +import hello.cluebackend.domain.assignment.presentation.dto.response.GetAllAssignmentDto; import hello.cluebackend.domain.assignment.domain.Assignment; import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; import hello.cluebackend.domain.assignment.persistence.AssignmentRepository; @@ -25,8 +24,8 @@ @Transactional(readOnly = true) public class AssignmentCommandService{ private final AssignmentRepository assignmentRepository; - private final ClassRoomService classRoomService; private final AssignmentAttachmentRepository assignmentAttachmentRepository; + private final ClassRoomService classRoomService; private final FileService fileService; // 과제 단일 조회 @@ -34,25 +33,7 @@ public AssignmentResponseDto findById(UUID assignmentId) { Assignment a = findByIdOrThrow(assignmentId); List assignmentAttachments = assignmentAttachmentRepository.findAllByAssignment(a); - List assignmentAttachmentDtos = assignmentAttachments.stream() - .map(aa -> AssignmentAttachmentDto.builder() - .type(aa.getType()) - .value(aa.getValue()) - .originalFileName(aa.getOriginalFileName()) - .contentType(aa.getContentType()) - .size(aa.getSize()) - .build()) - .toList(); - - return AssignmentResponseDto.builder() - .assignmentId(a.getAssignmentId()) - .title(a.getTitle()) - .content(a.getContent()) - .startDate(a.getStartDate()) - .endDate(a.getEndDate()) - .userName(a.getUser().getUsername()) - .xAssignmentResponseDtos(assignmentAttachmentDtos) - .build(); + return AssignmentResponseDto.from(a, assignmentAttachments); } // 과제 전체 조회 @@ -64,28 +45,11 @@ public List findAllById(UUID userId, UUID classId) { .toList(); } - // 과제 ID를 통한 조회 - public Assignment findByIdOrThrow(UUID assignmentId) { - return assignmentRepository.findById(assignmentId) - .orElseThrow(() -> new EntityNotFoundException("해당 과제를 찾을수 없습니다.")); - } - - public AssignmentAttachment findAssignmentAttachmentByIdOrderThrow(UUID attachmentId){ - return assignmentAttachmentRepository.findById(attachmentId) - .orElseThrow(() -> new EntityNotFoundException("해당 첨부 파일을 찾을수 없습니다.")); - } - // 사용자가 속한 모든 수업 과제 조회 public List findAllAssignmentMe(UUID userId) { List assignments = assignmentRepository.getAllByUser(userId); return assignments.stream() - .map(a -> GetAllAssignmentDto.builder() - .assignmentId(a.getAssignmentId()) - .title(a.getTitle()) - .startDate(a.getStartDate()) - .endDate(a.getEndDate()) - .build() - ) + .map(a -> GetAllAssignmentDto.from(a)) .toList(); } @@ -93,4 +57,15 @@ public Resource downloadAttachment(AssignmentAttachment assignmentAttachment) th String path = assignmentAttachment.getValue(); return fileService.downloadFile(path); } + + // 과제 ID를 통한 조회 + public Assignment findByIdOrThrow(UUID assignmentId) { + return assignmentRepository.findById(assignmentId) + .orElseThrow(() -> new EntityNotFoundException("해당 과제를 찾을수 없습니다.")); + } + + public AssignmentAttachment findAssignmentAttachmentByIdOrderThrow(UUID attachmentId){ + return assignmentAttachmentRepository.findById(attachmentId) + .orElseThrow(() -> new EntityNotFoundException("해당 첨부 파일을 찾을수 없습니다.")); + } } diff --git a/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentQueryService.java b/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentQueryService.java index 5e50cc3a..34a670ae 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentQueryService.java +++ b/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentQueryService.java @@ -1,8 +1,8 @@ package hello.cluebackend.domain.assignment.application; -import hello.cluebackend.domain.assignment.api.dto.request.AssignmentAttachmentDto; -import hello.cluebackend.domain.assignment.api.dto.request.CreateAssignmentDto; -import hello.cluebackend.domain.assignment.api.dto.request.ModifyAssignmentDto; +import hello.cluebackend.domain.assignment.presentation.dto.request.AssignmentAttachmentDto; +import hello.cluebackend.domain.assignment.presentation.dto.request.CreateAssignmentDto; +import hello.cluebackend.domain.assignment.presentation.dto.request.ModifyAssignmentDto; import hello.cluebackend.domain.assignment.domain.Assignment; import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; import hello.cluebackend.domain.assignment.domain.FileType; @@ -64,7 +64,7 @@ public void delete(UUID assignmentId) { @Transactional public UUID patchAssignment(UUID assignmentId, ModifyAssignmentDto dto){ Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); - assignment.patch(dto.getTitle(), dto.getContent(), dto.getStartDate(), dto.getEndDate()); + assignment.updateDetails(dto.getTitle(), dto.getContent(), dto.getStartDate(), dto.getEndDate()); return assignment.getAssignmentId(); } diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java b/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java index af4598cc..c4814fe6 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java @@ -18,9 +18,9 @@ @Table(name = "assignment") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Builder @AllArgsConstructor -public class Assignment extends BaseEntity { +@Builder +public class Assignment { @Id @GeneratedValue(strategy = GenerationType.UUID) @Column(name = "assignment_id", nullable = false, updatable = false) @@ -46,7 +46,6 @@ public class Assignment extends BaseEntity { @JsonFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime endDate; - @OneToMany(mappedBy = "assignment", cascade = CascadeType.REMOVE, orphanRemoval = true) @Builder.Default @JsonIgnore @@ -63,12 +62,32 @@ public static Assignment create(ClassRoom classRoom, UserEntity user, String tit .endDate(endDate) .build(); } + public static Assignment of(ClassRoom classRoom, UserEntity user, String title, String content, LocalDateTime startDate, LocalDateTime endDate) { + return Assignment.builder() + .classRoom(classRoom) + .user(user) + .title(title) + .content(content) + .startDate(startDate) + .endDate(endDate) + .build(); + } + - // patch 메서드 (null 체크 후 업데이트) - public void patch(String title, String content, LocalDateTime startDate, LocalDateTime endDate) { + public void updateDetails(String title, String content, LocalDateTime startDate, LocalDateTime endDate) { if (title != null) this.title = title; if (content != null) this.content = content; if (startDate != null) this.startDate = startDate; if (endDate != null) this.endDate = endDate; } + + public void addSubmission(Submission submission){ + submissions.add(submission); + submission.setAssignment(this); + } + + public void removeSubmission(Submission submission){ + submissions.remove(submission); + submission.setAssignment(null); + } } \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java index 07843643..08090910 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java @@ -11,7 +11,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AssignmentAttachment extends BaseEntity { +public class AssignmentAttachment { @Id @GeneratedValue(strategy = GenerationType.UUID) @Column(name = "assignment_attachment_id", nullable = false, updatable = false) diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/BaseEntity.java b/src/main/java/hello/cluebackend/domain/assignment/domain/BaseEntity.java deleted file mode 100644 index 1beba01c..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/BaseEntity.java +++ /dev/null @@ -1,32 +0,0 @@ -package hello.cluebackend.domain.assignment.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.EntityListeners; -import jakarta.persistence.MappedSuperclass; -import lombok.Getter; -import org.springframework.data.annotation.CreatedBy; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedBy; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import java.time.LocalDateTime; - -@EntityListeners(AuditingEntityListener.class) -@MappedSuperclass -@Getter -public class BaseEntity { - @CreatedDate - @Column - private LocalDateTime createdDate; - - @LastModifiedDate - private LocalDateTime lastModified; - - @CreatedBy - @Column(updatable = false) - private String createdBy; - - @LastModifiedBy - private String lastModifiedBy; -} diff --git a/src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentRepository.java b/src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentRepository.java index 0c493793..3aa904bb 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentRepository.java +++ b/src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentRepository.java @@ -2,6 +2,7 @@ import hello.cluebackend.domain.assignment.domain.Assignment; import hello.cluebackend.domain.classroom.domain.ClassRoom; +import hello.cluebackend.domain.user.domain.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -19,4 +20,6 @@ public interface AssignmentRepository extends JpaRepository { List getAllByUser(@Param("userId") UUID userId); List findAllByClassRoom(ClassRoom classRoom); + + List findAllByUserAndClassRoom(UserEntity user, ClassRoom classRoom); } \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentCommandController.java similarity index 86% rename from src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java rename to src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentCommandController.java index 7f23546c..ce8978dc 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentCommandController.java @@ -1,13 +1,13 @@ -package hello.cluebackend.domain.assignment.api; +package hello.cluebackend.domain.assignment.presentation; -import hello.cluebackend.domain.assignment.api.dto.response.*; +import hello.cluebackend.domain.assignment.presentation.dto.response.*; import hello.cluebackend.domain.assignment.application.AssignmentCommandService; import hello.cluebackend.domain.assignment.domain.Assignment; import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; import hello.cluebackend.domain.assignment.exception.AccessDeniedException; import hello.cluebackend.domain.classroomuser.application.ClassroomUserService; -import hello.cluebackend.domain.submission.api.dto.response.SubmissionAttachmentDto; import hello.cluebackend.domain.submission.application.SubmissionCommandService; +import hello.cluebackend.domain.user.service.UserService; import hello.cluebackend.global.common.annotation.CurrentUser; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -30,13 +30,11 @@ @RequestMapping("/api/assignments") @RequiredArgsConstructor @Slf4j - -// TODO : 권한 확인 애노테이션 추가 필요" - public class AssignmentCommandController { private final ClassroomUserService classroomUserService; private final AssignmentCommandService assignmentCommandService; private final SubmissionCommandService submissionCommandService; + private final UserService userService; // 과제 단일 조회 @GetMapping("/{assignmentId}") @@ -44,12 +42,12 @@ public ResponseEntity getAssignment( @CurrentUser UUID userId, @PathVariable UUID assignmentId ) { - AssignmentResponseDto result = assignmentCommandService.findById(assignmentId); Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); UUID classroomId = assignment.getClassRoom().getClassRoomId(); if (!classroomUserService.isUserInClassroom(classroomId, userId)) { throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다."); } + AssignmentResponseDto result = assignmentCommandService.findById(assignmentId); return ResponseEntity.ok(result); } @@ -75,13 +73,6 @@ public ResponseEntity> getAllAssignments(@CurrentUser return ResponseEntity.ok(result); } - // 첨부 파일 혹은 링크 전체 조회 (선생, 학생) - @GetMapping("/{submissionId}/attachment") - public ResponseEntity> findAllAssignments(@CurrentUser UUID userId, @PathVariable UUID submissionId) { - List result = submissionCommandService.findAllAssignment(submissionId); - return ResponseEntity.ok(result); - } - // 첨부 파일 다운로드 @GetMapping("/{assignmentAttachmentId}/download") public ResponseEntity assignmentAttachmentDownload( diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentQueryController.java similarity index 90% rename from src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java rename to src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentQueryController.java index 0cecf9a6..48a8e695 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentQueryController.java @@ -1,8 +1,8 @@ -package hello.cluebackend.domain.assignment.api; +package hello.cluebackend.domain.assignment.presentation; -import hello.cluebackend.domain.assignment.api.dto.request.AssignmentAttachmentDto; -import hello.cluebackend.domain.assignment.api.dto.request.CreateAssignmentDto; -import hello.cluebackend.domain.assignment.api.dto.request.ModifyAssignmentDto; +import hello.cluebackend.domain.assignment.presentation.dto.request.AssignmentAttachmentDto; +import hello.cluebackend.domain.assignment.presentation.dto.request.CreateAssignmentDto; +import hello.cluebackend.domain.assignment.presentation.dto.request.ModifyAssignmentDto; import hello.cluebackend.domain.assignment.application.AssignmentQueryService; import hello.cluebackend.domain.assignment.domain.Assignment; import hello.cluebackend.domain.submission.application.SubmissionQueryService; diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentAttachmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentAttachmentDto.java new file mode 100644 index 00000000..04e38ab9 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentAttachmentDto.java @@ -0,0 +1,5 @@ +package hello.cluebackend.domain.assignment.presentation.dto.request; + +public record AssignmentAttachmentDto( + String url +) {} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/CreateAssignmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/CreateAssignmentDto.java similarity index 89% rename from src/main/java/hello/cluebackend/domain/assignment/api/dto/request/CreateAssignmentDto.java rename to src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/CreateAssignmentDto.java index 29cef95d..743b98f0 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/CreateAssignmentDto.java +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/CreateAssignmentDto.java @@ -1,4 +1,4 @@ -package hello.cluebackend.domain.assignment.api.dto.request; +package hello.cluebackend.domain.assignment.presentation.dto.request; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/ModifyAssignmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/ModifyAssignmentDto.java similarity index 88% rename from src/main/java/hello/cluebackend/domain/assignment/api/dto/request/ModifyAssignmentDto.java rename to src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/ModifyAssignmentDto.java index 02a4695d..e6342a52 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/ModifyAssignmentDto.java +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/ModifyAssignmentDto.java @@ -1,4 +1,4 @@ -package hello.cluebackend.domain.assignment.api.dto.request; +package hello.cluebackend.domain.assignment.presentation.dto.request; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentAttachmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentAttachmentDto.java new file mode 100644 index 00000000..9e499ec1 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentAttachmentDto.java @@ -0,0 +1,22 @@ +package hello.cluebackend.domain.assignment.presentation.dto.response; + +import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; +import hello.cluebackend.domain.assignment.domain.FileType; + +public record AssignmentAttachmentDto( + FileType type, + String value, + String originalFileName, + String contentType, + Long size +) { + public static AssignmentAttachmentDto from(AssignmentAttachment assignmentAttachment){ + return new AssignmentAttachmentDto( + assignmentAttachment.getType(), + assignmentAttachment.getValue(), + assignmentAttachment.getOriginalFileName(), + assignmentAttachment.getContentType(), + assignmentAttachment.getSize() + ); + } +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentResponseDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentResponseDto.java new file mode 100644 index 00000000..8a44d3e8 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentResponseDto.java @@ -0,0 +1,37 @@ +package hello.cluebackend.domain.assignment.presentation.dto.response; + +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +public record AssignmentResponseDto( + UUID assignmentId, + String title, + String content, + LocalDateTime startDate, + LocalDateTime endDate, + String userName, + + // 과제 첨부 데이터 + List attachmentDtos +) { + public static AssignmentResponseDto from(Assignment assignment, List assignmentAttachments){ + + List assignmentResponseDtos = assignmentAttachments.stream() + .map(aa -> AssignmentAttachmentDto.from(aa)) + .toList(); + + return new AssignmentResponseDto( + assignment.getAssignmentId(), + assignment.getTitle(), + assignment.getContent(), + assignment.getStartDate(), + assignment.getEndDate(), + assignment.getUser().getUsername(), + assignmentResponseDtos + ); + } +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAllAssignmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAllAssignmentDto.java new file mode 100644 index 00000000..19cba86c --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAllAssignmentDto.java @@ -0,0 +1,23 @@ +package hello.cluebackend.domain.assignment.presentation.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import hello.cluebackend.domain.assignment.domain.Assignment; + +import java.time.LocalDateTime; +import java.util.UUID; + +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 // 마감일 +) { + public static GetAllAssignmentDto from(Assignment assignment){ + return new GetAllAssignmentDto ( + assignment.getAssignmentId(), + assignment.getTitle(), + assignment.getStartDate(), + assignment.getEndDate() + ); + } +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/classroom/domain/ClassRoom.java b/src/main/java/hello/cluebackend/domain/classroom/domain/ClassRoom.java index bbe03f99..99aaca35 100644 --- a/src/main/java/hello/cluebackend/domain/classroom/domain/ClassRoom.java +++ b/src/main/java/hello/cluebackend/domain/classroom/domain/ClassRoom.java @@ -20,8 +20,7 @@ @AllArgsConstructor @NoArgsConstructor public class ClassRoom { - @Id - @GeneratedValue(strategy = GenerationType.UUID) + @Id @GeneratedValue(strategy = GenerationType.UUID) @Column(name = "class_room_id", nullable = false, updatable = false) private UUID classRoomId; diff --git a/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAssignmentAttachmentDto.java b/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAssignmentAttachmentDto.java deleted file mode 100644 index 998b45a0..00000000 --- a/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAssignmentAttachmentDto.java +++ /dev/null @@ -1,4 +0,0 @@ -package hello.cluebackend.domain.submission.api.dto.request; - -public record SubmissionAssignmentAttachmentDto() { -} diff --git a/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAttachmentUrlDto.java b/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAttachmentUrlDto.java deleted file mode 100644 index 3b1fd081..00000000 --- a/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAttachmentUrlDto.java +++ /dev/null @@ -1,5 +0,0 @@ -package hello.cluebackend.domain.submission.api.dto.request; - -public record SubmissionAttachmentUrlDto( - String url -) { } diff --git a/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionAttachmentDto.java b/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionAttachmentDto.java deleted file mode 100644 index c1300e25..00000000 --- a/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionAttachmentDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package hello.cluebackend.domain.submission.api.dto.response; - -import hello.cluebackend.domain.submission.domain.fileType; -import lombok.Builder; - -@Builder -public record SubmissionAttachmentDto( - fileType type, - String value, - String originalFileName, - String contentType, - Long size -) { } diff --git a/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionDto.java b/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionDto.java deleted file mode 100644 index 345e3001..00000000 --- a/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionDto.java +++ /dev/null @@ -1,22 +0,0 @@ -package hello.cluebackend.domain.submission.api.dto.response; - -import lombok.*; - -import java.time.LocalDateTime; - -@AllArgsConstructor -@Data -@Builder -public class SubmissionDto { - // 과제 데이터 - private String title; - private LocalDateTime startDate; - private LocalDateTime endDate; - - // 유저 정보 - private String userName; - - // 제출 정보 - private boolean isSubmitted; - private LocalDateTime submittedAt; -} diff --git a/src/main/java/hello/cluebackend/domain/submission/application/SubmissionCommandService.java b/src/main/java/hello/cluebackend/domain/submission/application/SubmissionCommandService.java index d75cf4f7..3ea77b1e 100644 --- a/src/main/java/hello/cluebackend/domain/submission/application/SubmissionCommandService.java +++ b/src/main/java/hello/cluebackend/domain/submission/application/SubmissionCommandService.java @@ -1,16 +1,19 @@ package hello.cluebackend.domain.submission.application; -import hello.cluebackend.domain.assignment.api.dto.response.SubmissionCheck; +import hello.cluebackend.domain.assignment.exception.AccessDeniedException; +import hello.cluebackend.domain.classroom.domain.ClassRoom; +import hello.cluebackend.domain.classroom.service.ClassRoomService; import hello.cluebackend.domain.assignment.application.AssignmentCommandService; import hello.cluebackend.domain.assignment.domain.Assignment; -import hello.cluebackend.domain.assignment.persistence.AssignmentRepository; import hello.cluebackend.domain.file.service.FileService; -import hello.cluebackend.domain.submission.api.dto.response.SubmissionAttachmentDto; -import hello.cluebackend.domain.submission.api.dto.response.SubmissionDto; +import hello.cluebackend.domain.submission.presentation.dto.response.*; import hello.cluebackend.domain.submission.domain.Submission; import hello.cluebackend.domain.submission.domain.SubmissionAttachment; import hello.cluebackend.domain.submission.persistence.SubmissionRepository; import hello.cluebackend.domain.submission.persistence.SubmissionAttachmentRepository; +import hello.cluebackend.domain.user.domain.Role; +import hello.cluebackend.domain.user.domain.UserEntity; +import hello.cluebackend.domain.user.service.UserService; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import org.springframework.core.io.Resource; @@ -25,92 +28,87 @@ public class SubmissionCommandService { private final SubmissionRepository submissionRepository; private final SubmissionAttachmentRepository submissionAttachmentRepository; - private final AssignmentRepository assignmentRepository; + private final ClassRoomService classRoomService; private final AssignmentCommandService assignmentCommandService; private final FileService fileService; + private final UserService userService; - // 과제 제출 여부 확인 API - public List checkAssignment(UUID userId, UUID assignmentId) { - Assignment assignment = assignmentRepository.findById(assignmentId) - .orElseThrow(() -> new EntityNotFoundException("해당 과제를 찾을수 없습니다.")); - List submissions = submissionRepository.findAllByAssignment(assignment); - return submissions.stream() - .filter(s -> s.getUser().getUserId().equals(userId)) - .map(s -> SubmissionCheck.builder() - .submissionId(s.getSubmissionId()) - .userName(s.getUser().getUsername()) - .classNumberGrade(s.getUser().getClassCode()) - .isSubmitted(s.getIsSubmitted()) - .submittedAt(s.getSubmittedAt()) - .build() - ) - .toList(); - } + // 과제 전체 조회 및 과제 첨부 파일 조회 + public List findAllByAssignmentId(UUID userId,UUID classId) { + UserEntity user = userService.findById(userId).toEntity(); + ClassRoom classRoom = classRoomService.findById(classId).toEntity(); - public List findAllByAssignmentId(UUID userId, UUID assignmentId) { - Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); - List submissions = submissionRepository.findAllByAssignment(assignment); + List submissions = submissionRepository.findAllByClassRoomAndUser(classRoom, user); return submissions.stream() - .map(s -> SubmissionDto.builder() - .title(s.getAssignment().getTitle()) - .startDate(s.getAssignment().getStartDate()) - .endDate(s.getAssignment().getEndDate()) - .userName(s.getUser().getUsername()) - .isSubmitted(s.getIsSubmitted()) - .submittedAt(s.getSubmittedAt()) - .build() - ) + .map(submission -> { + List submissionAttachmentResponses = + submissionAttachmentRepository.findAllBySubmission(submission).stream() + .map(SubmissionAttachmentResponse::from) + .toList(); + + return SubmissionResponse.from(submission, submissionAttachmentResponses); + }) .toList(); } - // - public SubmissionDto findByAssignmentId(UUID assignmentId) { - Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); - - Submission s = submissionRepository.findByAssignment(assignment); + // 과제 제출 단일 조회 + public SubmissionResponse findByAssignmentId(UUID userId, UUID submissionId) { + Submission submission = findByIdOrThrow(submissionId); - return SubmissionDto.builder() - .title(s.getAssignment().getTitle()) - .startDate(s.getAssignment().getStartDate()) - .endDate(s.getAssignment().getEndDate()) - .userName(s.getUser().getUsername()) - .isSubmitted(s.getIsSubmitted()) - .submittedAt(s.getSubmittedAt()) - .build(); - } + if (!submission.getUser().getUserId().equals(userId) && !submission.getUser().getRole().equals(Role.TEACHER)) { + throw new AccessDeniedException("사용자가 제출한 과제가 아닙니다."); + } - public Submission findByIdOrThrow(UUID submissionId) { - return submissionRepository.findById(submissionId) - .orElseThrow(() -> new EntityNotFoundException("해당 제출 과제를 찾을수 없습니다.")); + List submissionAttachments = submissionAttachmentRepository.findAllBySubmission(submission); + List submissionAttachmentResponses = submissionAttachments.stream() + .map(SubmissionAttachmentResponse::from) + .toList(); + return SubmissionResponse.from(submission, submissionAttachmentResponses); } - public SubmissionAttachment findAssignmentAttachmentByIdOrThrow(UUID submissionAttachmentId){ - return submissionAttachmentRepository.findById(submissionAttachmentId) - .orElseThrow(() -> new EntityNotFoundException("해당 제출 과제를 찾을수 없습니다.")); + // 전체 학생 과제 제출 여부 (선생) + public List checkAssignment(UUID userId, UUID assignmentId) { + Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); + List submissions = submissionRepository.findAllByAssignment(assignment); + return submissions.stream() + .filter(s -> s.getUser().getUserId().equals(userId)) + .map(s -> SubmissionCheck.from(s)) + .toList(); } - public List findAllAssignment(UUID submissionId) { + // 첨부 파일 혹은 링크 전체 조회 (학생) + public List findAllAssignmentStudent(UUID userId, UUID submissionId) { Submission submission = findByIdOrThrow(submissionId); List attachments = submissionAttachmentRepository.findAllBySubmission(submission); return attachments.stream() - .map(sa -> SubmissionAttachmentDto.builder() - .type(sa.getType()) - .value(sa.getValue()) - .originalFileName(sa.getOriginalFileName()) - .contentType(sa.getContentType()) - .size(sa.getSize()) - .build() - ).toList(); + .filter(sa -> sa.getUser().getUserId().equals(userId)) + .map(sa -> SubmissionAttachmentResponse.from(sa)) + .toList(); } - public SubmissionAttachment findsubmissionAttachmentByIdOrThrow(UUID submissionAttachmentId) { - return submissionAttachmentRepository.findById(submissionAttachmentId) - .orElseThrow(() -> new EntityNotFoundException("해당 과제 제출 첨부파일을 찾을수 없습니다.")); + // 첨부 파일 혹은 링크 전체 조회 (선생) + public List findAllAssignmentTeacher(UUID submissionId) { + Submission submission = findByIdOrThrow(submissionId); + List attachments = submissionAttachmentRepository.findAllBySubmission(submission); + return attachments.stream() + .map(sa -> SubmissionAttachmentResponse.from(sa)) + .toList(); } + // 첨부파일 다운로드 public Resource downloadAttachment(SubmissionAttachment submissionAttachment) throws IOException { String path = submissionAttachment.getValue(); return fileService.downloadFile(path); } + + public Submission findByIdOrThrow(UUID submissionId) { + return submissionRepository.findById(submissionId) + .orElseThrow(() -> new EntityNotFoundException("해당 제출 과제를 찾을수 없습니다.")); + } + + public SubmissionAttachment findSubmissionAttachmentByIdOrThrow(UUID submissionAttachmentId) { + return submissionAttachmentRepository.findById(submissionAttachmentId) + .orElseThrow(() -> new EntityNotFoundException("해당 과제 제출 첨부파일을 찾을수 없습니다.")); + } } diff --git a/src/main/java/hello/cluebackend/domain/submission/application/SubmissionQueryService.java b/src/main/java/hello/cluebackend/domain/submission/application/SubmissionQueryService.java index 66ded74c..3aa471ac 100644 --- a/src/main/java/hello/cluebackend/domain/submission/application/SubmissionQueryService.java +++ b/src/main/java/hello/cluebackend/domain/submission/application/SubmissionQueryService.java @@ -1,8 +1,8 @@ package hello.cluebackend.domain.submission.application; import hello.cluebackend.domain.assignment.domain.Assignment; -import hello.cluebackend.domain.submission.api.dto.request.SubmissionAttachmentUrlDto; -import hello.cluebackend.domain.submission.domain.fileType; +import hello.cluebackend.domain.submission.presentation.dto.request.SubmissionAttachmentUrlDto; +import hello.cluebackend.domain.submission.domain.FileType; import hello.cluebackend.domain.file.service.FileService; import hello.cluebackend.domain.submission.domain.Submission; import hello.cluebackend.domain.submission.domain.SubmissionAttachment; @@ -36,7 +36,7 @@ public void assignToAllStudentsInClassroom(UUID classroomId, Assignment assignme ClassRoom classRoom = classRoomService.findById(classroomId).toEntity(); List users = classroomUserService.findAllClassroomUser(classRoom); List submissions = users.stream() - .map(u -> new Submission(assignment, u, false, null)) + .map(u -> new Submission(assignment, u,assignment.getClassRoom(), false, null)) .toList(); submissionRepository.saveAll(submissions); } @@ -46,7 +46,7 @@ public void assignToAllStudentsInClassroom(UUID classroomId, Assignment assignme public Submission submitSubmission(UUID submissionId) { Submission submission = submissionCommandService.findByIdOrThrow(submissionId); submission.submit(); - return submissionRepository.save(submission); + return submission; } // 과제 제출 취소하기 @@ -54,10 +54,9 @@ public Submission submitSubmission(UUID submissionId) { public Submission cancelSubmission(UUID submissionId) { Submission submission = submissionCommandService.findByIdOrThrow(submissionId); submission.cancel(); - return submissionRepository.save(submission); + return submission; } - // 첨부 파일 추가 @Transactional public SubmissionAttachment fileUpload(UUID submissionId, MultipartFile file) { @@ -67,7 +66,7 @@ public SubmissionAttachment fileUpload(UUID submissionId, MultipartFile file) { SubmissionAttachment result = SubmissionAttachment.builder() .submission(submission) - .type(fileType.file) + .type(FileType.FILE) .value(storedFiledName) .originalFileName(file.getOriginalFilename()) .contentType(file.getContentType()) @@ -84,7 +83,7 @@ public SubmissionAttachment linkUpload(UUID submissionId, SubmissionAttachmentUr SubmissionAttachment submissionAttachment = SubmissionAttachment.builder() .submission(submission) - .type(fileType.url) + .type(FileType.URL) .value(dto.url()) .build(); @@ -95,8 +94,8 @@ public SubmissionAttachment linkUpload(UUID submissionId, SubmissionAttachmentUr // 첨부 파일 삭제 @Transactional public void deleteSubmissionAttachment(UUID submissionAttachmentId) { - SubmissionAttachment submissionAttachment = submissionCommandService.findAssignmentAttachmentByIdOrThrow(submissionAttachmentId); - if(submissionAttachment.getType() == fileType.file){ + SubmissionAttachment submissionAttachment = submissionCommandService.findSubmissionAttachmentByIdOrThrow(submissionAttachmentId); + if(submissionAttachment.getType() == FileType.FILE){ fileService.deleteFile(submissionAttachment.getValue()); } submissionAttachmentRepository.delete(submissionAttachment); diff --git a/src/main/java/hello/cluebackend/domain/submission/domain/BaseEntity.java b/src/main/java/hello/cluebackend/domain/submission/domain/BaseEntity.java deleted file mode 100644 index 1d1706cc..00000000 --- a/src/main/java/hello/cluebackend/domain/submission/domain/BaseEntity.java +++ /dev/null @@ -1,32 +0,0 @@ -package hello.cluebackend.domain.submission.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.EntityListeners; -import jakarta.persistence.MappedSuperclass; -import lombok.Getter; -import org.springframework.data.annotation.CreatedBy; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedBy; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import java.time.LocalDateTime; - -@EntityListeners(AuditingEntityListener.class) -@MappedSuperclass -@Getter -public class BaseEntity { - @CreatedDate - @Column - private LocalDateTime createdDate; - - @LastModifiedDate - private LocalDateTime lastModified; - - @CreatedBy - @Column(updatable = false) - private String createdBy; - - @LastModifiedBy - private String lastModifiedBy; -} diff --git a/src/main/java/hello/cluebackend/domain/submission/domain/fileType.java b/src/main/java/hello/cluebackend/domain/submission/domain/FileType.java similarity index 59% rename from src/main/java/hello/cluebackend/domain/submission/domain/fileType.java rename to src/main/java/hello/cluebackend/domain/submission/domain/FileType.java index 661ba614..caae862e 100644 --- a/src/main/java/hello/cluebackend/domain/submission/domain/fileType.java +++ b/src/main/java/hello/cluebackend/domain/submission/domain/FileType.java @@ -1,5 +1,6 @@ package hello.cluebackend.domain.submission.domain; -public enum fileType { - file, url +public enum FileType { + FILE, + URL } diff --git a/src/main/java/hello/cluebackend/domain/submission/domain/Submission.java b/src/main/java/hello/cluebackend/domain/submission/domain/Submission.java index 89abc6de..6958bba3 100644 --- a/src/main/java/hello/cluebackend/domain/submission/domain/Submission.java +++ b/src/main/java/hello/cluebackend/domain/submission/domain/Submission.java @@ -1,6 +1,7 @@ package hello.cluebackend.domain.submission.domain; import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.classroom.domain.ClassRoom; import hello.cluebackend.domain.user.domain.UserEntity; import jakarta.persistence.*; import lombok.*; @@ -14,9 +15,8 @@ @AllArgsConstructor @Builder @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Submission extends BaseEntity{ - @Id - @GeneratedValue(strategy = GenerationType.UUID) +public class Submission { + @Id @GeneratedValue(strategy = GenerationType.UUID) @Column(name = "submission_id", nullable = false, updatable = false) private UUID submissionId; @@ -28,28 +28,27 @@ public class Submission extends BaseEntity{ @JoinColumn(name = "user_id") private UserEntity user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "class_room_id") + private ClassRoom classRoom; + @Column(name = "is_submitted") private boolean isSubmitted; @Column(name = "submitted_at", nullable = true) private LocalDateTime submittedAt; - public Submission(Assignment assignment, UserEntity user, boolean isSubmitted, LocalDateTime submittedAt) { - if(assignment != null) { changeAssignment(assignment); } + public Submission(Assignment assignment, UserEntity user, ClassRoom classRoom ,boolean isSubmitted, LocalDateTime submittedAt) { + this.assignment = assignment; this.user = user; + this.classRoom = classRoom; this.isSubmitted = isSubmitted; this.submittedAt = submittedAt; } - public void changeAssignment(Assignment assignment) { - this.assignment = assignment; - assignment.getSubmissions().add(this); - } - // 과제 제출 취소 public void cancel() { this.isSubmitted = false; - this.submittedAt = LocalDateTime.now(); } // 과제 제출 diff --git a/src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java b/src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java index c7c5a15b..de62b80b 100644 --- a/src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java +++ b/src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java @@ -3,7 +3,6 @@ import hello.cluebackend.domain.user.domain.UserEntity; import jakarta.persistence.*; import lombok.*; -import org.springframework.core.io.Resource; import java.util.UUID; @@ -29,7 +28,7 @@ public class SubmissionAttachment { @Column(nullable = false) @Enumerated(EnumType.STRING) - private fileType type; + private FileType type; @Column(nullable = false) private String value; diff --git a/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionAttachmentRepository.java b/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionAttachmentRepository.java index 6dc23e96..249c9689 100644 --- a/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionAttachmentRepository.java +++ b/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionAttachmentRepository.java @@ -1,6 +1,5 @@ package hello.cluebackend.domain.submission.persistence; -import hello.cluebackend.domain.assignment.domain.Assignment; import hello.cluebackend.domain.submission.domain.Submission; import hello.cluebackend.domain.submission.domain.SubmissionAttachment; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionRepository.java b/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionRepository.java index a13d5c44..0173df98 100644 --- a/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionRepository.java +++ b/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionRepository.java @@ -1,7 +1,10 @@ package hello.cluebackend.domain.submission.persistence; import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.classroom.domain.ClassRoom; import hello.cluebackend.domain.submission.domain.Submission; +import hello.cluebackend.domain.submission.domain.SubmissionAttachment; +import hello.cluebackend.domain.user.domain.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -12,5 +15,7 @@ public interface SubmissionRepository extends JpaRepository { List findAllByAssignment(Assignment assignment); - Submission findByAssignment(Assignment assignment); + List findAllBySubmissionId(UUID submissionId); + + List findAllByClassRoomAndUser(ClassRoom classRoom, UserEntity user); } diff --git a/src/main/java/hello/cluebackend/domain/submission/api/SubmissionCommandController.java b/src/main/java/hello/cluebackend/domain/submission/presentation/SubmissionCommandController.java similarity index 64% rename from src/main/java/hello/cluebackend/domain/submission/api/SubmissionCommandController.java rename to src/main/java/hello/cluebackend/domain/submission/presentation/SubmissionCommandController.java index 87cfd27a..07c343ab 100644 --- a/src/main/java/hello/cluebackend/domain/submission/api/SubmissionCommandController.java +++ b/src/main/java/hello/cluebackend/domain/submission/presentation/SubmissionCommandController.java @@ -1,21 +1,16 @@ -package hello.cluebackend.domain.submission.api; +package hello.cluebackend.domain.submission.presentation; -import hello.cluebackend.domain.assignment.api.dto.response.SubmissionCheck; -import hello.cluebackend.domain.submission.api.dto.response.SubmissionDto; +import hello.cluebackend.domain.assignment.exception.AccessDeniedException; +import hello.cluebackend.domain.classroomuser.application.ClassroomUserService; import hello.cluebackend.domain.submission.application.SubmissionCommandService; import hello.cluebackend.domain.submission.domain.SubmissionAttachment; import hello.cluebackend.global.common.annotation.CurrentUser; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.Resource; -import org.springframework.http.ContentDisposition; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import hello.cluebackend.domain.submission.presentation.dto.response.*; +import org.springframework.http.*; +import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -28,25 +23,28 @@ @Slf4j public class SubmissionCommandController { private final SubmissionCommandService submissionCommandService; + private final ClassroomUserService classroomUserService; // 할당된 과제 전체 조회 - @GetMapping("/assignment/{assignmentId}") - public ResponseEntity> findAllSubmission( + @GetMapping("/{classId}") + public ResponseEntity> findSubmission( @CurrentUser UUID userId, - @PathVariable UUID assignmentId + @PathVariable UUID classId ) { - List result = submissionCommandService.findAllByAssignmentId(userId,assignmentId); + if (!classroomUserService.isUserInClassroom(classId, userId)) { + throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다."); + } + List result = submissionCommandService.findAllByAssignmentId(userId,classId); return ResponseEntity.ok(result); } - // 할당된 과제 단일 조회 - @GetMapping("/{submissionId}") - public ResponseEntity findSubmission( + // 할당된 과제 조회 + @GetMapping("/assignment/{submissionId}") + public ResponseEntity findAllSubmission( @CurrentUser UUID userId, - @PathVariable UUID assignmentId + @PathVariable UUID submissionId ) { - SubmissionDto result = submissionCommandService.findByAssignmentId(assignmentId); - + SubmissionResponse result = submissionCommandService.findByAssignmentId(userId, submissionId); return ResponseEntity.ok(result); } @@ -66,7 +64,7 @@ public ResponseEntity submissionAttachmentDownload( @CurrentUser UUID userId, @PathVariable UUID submissionAttachmentId ) throws IOException { - SubmissionAttachment submissionAttachment = submissionCommandService.findsubmissionAttachmentByIdOrThrow(submissionAttachmentId); + SubmissionAttachment submissionAttachment = submissionCommandService.findSubmissionAttachmentByIdOrThrow(submissionAttachmentId); Resource resource = submissionCommandService.downloadAttachment(submissionAttachment); String original = submissionAttachment.getOriginalFileName(); diff --git a/src/main/java/hello/cluebackend/domain/submission/api/SubmissionQueryController.java b/src/main/java/hello/cluebackend/domain/submission/presentation/SubmissionQueryController.java similarity index 79% rename from src/main/java/hello/cluebackend/domain/submission/api/SubmissionQueryController.java rename to src/main/java/hello/cluebackend/domain/submission/presentation/SubmissionQueryController.java index 289d4d88..56412394 100644 --- a/src/main/java/hello/cluebackend/domain/submission/api/SubmissionQueryController.java +++ b/src/main/java/hello/cluebackend/domain/submission/presentation/SubmissionQueryController.java @@ -1,6 +1,7 @@ -package hello.cluebackend.domain.submission.api; +package hello.cluebackend.domain.submission.presentation; -import hello.cluebackend.domain.submission.api.dto.request.SubmissionAttachmentUrlDto; +import hello.cluebackend.domain.submission.presentation.dto.request.SubmissionAttachmentUrlDto; +import hello.cluebackend.domain.submission.presentation.dto.response.SubmissionDto; import hello.cluebackend.domain.submission.application.SubmissionQueryService; import hello.cluebackend.domain.submission.domain.Submission; import hello.cluebackend.domain.submission.domain.SubmissionAttachment; @@ -23,27 +24,27 @@ public class SubmissionQueryController { // 과제 제출 하기 @PatchMapping("/{submissionId}/submit") - public ResponseEntity submitSubmission( + public ResponseEntity submitSubmission( @CurrentUser UUID userId, @PathVariable UUID submissionId ) { Submission submission = submissionQueryService.submitSubmission(submissionId); - return ResponseEntity.ok(submission); + return ResponseEntity.ok(SubmissionDto.from(submission)); } // 과제 제출 취소하기 @PatchMapping("/{submissionId}/cancel") - public ResponseEntity cancelSubmission( + public ResponseEntity cancelSubmission( @CurrentUser UUID userId, @PathVariable UUID submissionId ){ Submission submission = submissionQueryService.cancelSubmission(submissionId); - return ResponseEntity.ok(submission); + return ResponseEntity.ok(SubmissionDto.from(submission)); } - + // 과제 첨부파일 삭제하기 @DeleteMapping("/{submissionAttachmentId}") - public ResponseEntity deleteSubmission( + public ResponseEntity deleteSubmissionAttachment( @CurrentUser UUID userId, @PathVariable UUID submissionAttachmentId ){ @@ -64,7 +65,7 @@ public ResponseEntity fileUpload( // 과제 제출 첨부 링크 추가 @PostMapping("/{submisisonId}/link") - public ResponseEntity deleteFile( + public ResponseEntity linkUpload( @CurrentUser UUID userId, @PathVariable UUID submissionId, @RequestBody SubmissionAttachmentUrlDto dto diff --git a/src/main/java/hello/cluebackend/domain/submission/presentation/dto/request/SubmissionAttachmentUrlDto.java b/src/main/java/hello/cluebackend/domain/submission/presentation/dto/request/SubmissionAttachmentUrlDto.java new file mode 100644 index 00000000..92e7e43b --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/presentation/dto/request/SubmissionAttachmentUrlDto.java @@ -0,0 +1,5 @@ +package hello.cluebackend.domain.submission.presentation.dto.request; + +public record SubmissionAttachmentUrlDto( + String url +) { } diff --git a/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionAttachmentResponse.java b/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionAttachmentResponse.java new file mode 100644 index 00000000..5b64ae47 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionAttachmentResponse.java @@ -0,0 +1,24 @@ +package hello.cluebackend.domain.submission.presentation.dto.response; + +import hello.cluebackend.domain.submission.domain.FileType; +import hello.cluebackend.domain.submission.domain.SubmissionAttachment; +import lombok.Builder; + +@Builder +public record SubmissionAttachmentResponse( + FileType type, + String value, + String originalFileName, + String contentType, + Long size +) { + public static SubmissionAttachmentResponse from(SubmissionAttachment submissionAttachment){ + return new SubmissionAttachmentResponse( + submissionAttachment.getType(), + submissionAttachment.getValue(), + submissionAttachment.getOriginalFileName(), + submissionAttachment.getContentType(), + submissionAttachment.getSize() + ); + } +} diff --git a/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionCheck.java b/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionCheck.java new file mode 100644 index 00000000..0373c5af --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionCheck.java @@ -0,0 +1,24 @@ +package hello.cluebackend.domain.submission.presentation.dto.response; + +import hello.cluebackend.domain.submission.domain.Submission; + +import java.time.LocalDateTime; +import java.util.UUID; + +public record SubmissionCheck ( + String userName, + int classNumberGrade, + UUID submissionId, + boolean isSubmitted, + LocalDateTime submittedAt +){ + public static SubmissionCheck from(Submission submission) { + return new SubmissionCheck( + submission.getUser().getUsername(), + submission.getUser().getClassCode(), + submission.getSubmissionId(), + submission.getIsSubmitted(), + submission.getSubmittedAt() + ); + } +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionDto.java b/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionDto.java new file mode 100644 index 00000000..4a6bb664 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionDto.java @@ -0,0 +1,20 @@ +package hello.cluebackend.domain.submission.presentation.dto.response; + +import hello.cluebackend.domain.submission.domain.Submission; + +import java.time.LocalDateTime; +import java.util.UUID; + +public record SubmissionDto( + UUID submissionId, + boolean IsSubmitted, + LocalDateTime submittedAt +) { + public static SubmissionDto from(Submission submission) { + return new SubmissionDto( + submission.getSubmissionId(), + submission.getIsSubmitted(), + submission.getSubmittedAt() + ); + } +} diff --git a/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionResponse.java b/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionResponse.java new file mode 100644 index 00000000..88b2c4a9 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/presentation/dto/response/SubmissionResponse.java @@ -0,0 +1,36 @@ +package hello.cluebackend.domain.submission.presentation.dto.response; + +import hello.cluebackend.domain.submission.domain.Submission; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +public record SubmissionResponse( + String title, + String content, + LocalDateTime startDate, + LocalDateTime endDate, + + String userName, + + UUID submissionId, + boolean IsSubmitted, + LocalDateTime submittedAt, + + List submissionAttachmentResponses +) { + public static SubmissionResponse from(Submission submission, List sa) { + return new SubmissionResponse( + submission.getAssignment().getTitle(), + submission.getAssignment().getContent(), + submission.getAssignment().getStartDate(), + submission.getAssignment().getEndDate(), + submission.getUser().getUsername(), + submission.getSubmissionId(), + submission.getIsSubmitted(), + submission.getSubmittedAt(), + sa + ); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 0cf7dffb..464903d8 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -2,12 +2,12 @@ spring: jpa: hibernate: - ddl-auto: validate + ddl-auto: update config: activate: on-profile: dev -logging: - level: - root: debug \ No newline at end of file +#logging: +# level: +# root: debug diff --git a/src/test/java/hello/cluebackend/domain/submission/presentation/SubmissionCommandControllerTest.java b/src/test/java/hello/cluebackend/domain/submission/presentation/SubmissionCommandControllerTest.java new file mode 100644 index 00000000..7b7096c5 --- /dev/null +++ b/src/test/java/hello/cluebackend/domain/submission/presentation/SubmissionCommandControllerTest.java @@ -0,0 +1,159 @@ +package hello.cluebackend.domain.submission.presentation; + +import hello.cluebackend.domain.classroomuser.application.ClassroomUserService; +import hello.cluebackend.domain.submission.application.SubmissionCommandService; +import hello.cluebackend.domain.submission.domain.SubmissionAttachment; +import hello.cluebackend.global.common.annotation.CurrentUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.core.MethodParameter; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.method.support.ModelAndViewContainer; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.UUID; + +import static org.hamcrest.Matchers.containsString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(SubmissionCommandController.class) +class SubmissionCommandControllerTest { + + private static final UUID FIXED_USER_ID = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); + + @Autowired + private MockMvc mockMvc; + + @MockitoBean private SubmissionCommandService submissionCommandService; + @MockitoBean private ClassroomUserService classroomUserService; + + @BeforeEach + void setUp(WebApplicationContext wac) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(wac) + .setCustomArgumentResolvers(new CurrentUserUuidArgumentResolver(FIXED_USER_ID)) + .build(); + } + + @Test + @DisplayName("GET /api/submissions/{classId} - 교실 멤버일 때 빈 리스트 200 OK") + void findSubmission_ok_emptyList() throws Exception { + UUID classId = UUID.randomUUID(); + + when(classroomUserService.isUserInClassroom(eq(classId), eq(FIXED_USER_ID))).thenReturn(true); + when(submissionCommandService.findAllByAssignmentId(eq(FIXED_USER_ID), eq(classId))) + .thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/api/submissions/{classId}", classId)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + } + + @Test + @DisplayName("GET /api/submissions/{classId} - 교실 멤버가 아닐 때 403 Forbidden") + void findSubmission_forbidden_whenNotClassMember() throws Exception { + UUID classId = UUID.randomUUID(); + + when(classroomUserService.isUserInClassroom(eq(classId), eq(FIXED_USER_ID))).thenReturn(false); + + mockMvc.perform(get("/api/submissions/{classId}", classId)) + .andExpect(status().isForbidden()); + } + + @Test + @DisplayName("GET /api/submissions/assignment/{submissionId} - 본인 제출물 단건 조회 200 OK (본문 null 허용)") + void findAllSubmission_ok_nullBodyAllowed() throws Exception { + UUID submissionId = UUID.randomUUID(); + + when(submissionCommandService.findByAssignmentId(eq(FIXED_USER_ID), eq(submissionId))) + .thenReturn(null); + + MockHttpServletResponse response = mockMvc.perform(get("/api/submissions/assignment/{submissionId}", submissionId)) + .andExpect(status().isOk()) + .andReturn() + .getResponse(); + + assert response.getContentAsByteArray().length == 0; + } + + @Test + @DisplayName("GET /api/submissions/{assignmentId}/check - 제출 여부 리스트 200 OK (빈 리스트)") + void checkAssignment_ok_emptyList() throws Exception { + UUID assignmentId = UUID.randomUUID(); + + when(submissionCommandService.checkAssignment(eq(FIXED_USER_ID), eq(assignmentId))) + .thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/api/submissions/{assignmentId}/check", assignmentId)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + } + + @Test + @DisplayName("GET /api/submissions/{submissionAttachmentId}/download - 첨부파일 다운로드 200 OK 및 헤더 검증") + void submissionAttachmentDownload_ok() throws Exception { + UUID submissionAttachmentId = UUID.randomUUID(); + + String originalFileName = "sample.txt"; + String contentType = MediaType.TEXT_PLAIN_VALUE; + byte[] fileBytes = "hello-file".getBytes(StandardCharsets.UTF_8); + Resource resource = new ByteArrayResource(fileBytes); + + SubmissionAttachment attachment = Mockito.mock(SubmissionAttachment.class); + when(attachment.getOriginalFileName()).thenReturn(originalFileName); + when(attachment.getContentType()).thenReturn(contentType); + when(attachment.getValue()).thenReturn("/fake/path/sample.txt"); + + when(submissionCommandService.findSubmissionAttachmentByIdOrThrow(eq(submissionAttachmentId))) + .thenReturn(attachment); + when(submissionCommandService.downloadAttachment(eq(attachment))) + .thenReturn(resource); + + mockMvc.perform(get("/api/submissions/{submissionAttachmentId}/download", submissionAttachmentId)) + .andExpect(status().isOk()) + .andExpect(header().string("Content-Type", containsString(MediaType.TEXT_PLAIN_VALUE))) + .andExpect(header().string("Content-Disposition", containsString("attachment"))) + .andExpect(header().string("Content-Disposition", containsString(originalFileName))); + } + + // @CurrentUser UUID 주입을 위한 간단한 ArgumentResolver + static class CurrentUserUuidArgumentResolver implements org.springframework.web.method.support.HandlerMethodArgumentResolver { + private final UUID fixedUserId; + + CurrentUserUuidArgumentResolver(UUID fixedUserId) { + this.fixedUserId = fixedUserId; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterAnnotation(CurrentUser.class) != null + && UUID.class.isAssignableFrom(parameter.getParameterType()); + } + + @Override + public Object resolveArgument(MethodParameter parameter, + @Nullable ModelAndViewContainer mavContainer, + org.springframework.web.context.request.NativeWebRequest webRequest, + @Nullable WebDataBinderFactory binderFactory) { + return fixedUserId; + } + } +} \ No newline at end of file