Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REFACTOR] 과제 삭제 연관관계 해결, 커스텀 예외처리 사용 #138

Merged
merged 7 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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 @@ -53,6 +53,9 @@ public class Assignment extends BaseTimeEntity {
@JoinColumn(name = "instructor_id")
private Instructor instructor;

@OneToMany(mappedBy = "assignment", cascade = CascadeType.REMOVE)
private List<Submission> submissions = new ArrayList<>();

@OneToMany(mappedBy = "assignment", cascade = CascadeType.ALL, orphanRemoval = true)
private List<AssignmentFile> files = new ArrayList<>();

Expand Down Expand Up @@ -95,4 +98,5 @@ public void addFile(AssignmentFile file) {
public void removeFile(AssignmentFile file) {
this.files.remove(file);
}

}
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
package com.example.epari.assignment.service;

import com.example.epari.admin.exception.CourseNotFoundException;
import com.example.epari.assignment.domain.Assignment;
import com.example.epari.assignment.domain.AssignmentFile;
import com.example.epari.assignment.domain.Submission;
import com.example.epari.assignment.domain.SubmissionFile;
import com.example.epari.assignment.dto.assignment.AssignmentRequestDto;
import com.example.epari.assignment.dto.assignment.AssignmentResponseDto;
import com.example.epari.assignment.repository.AssignmentFileRepository;
import com.example.epari.assignment.repository.AssignmentRepository;
import com.example.epari.assignment.repository.SubmissionRepository;
import com.example.epari.course.domain.Course;
import com.example.epari.course.repository.CourseRepository;
import com.example.epari.global.common.base.BaseUser;
import com.example.epari.global.common.repository.BaseUserRepository;
import com.example.epari.global.common.service.S3FileService;
import com.example.epari.global.exception.assignment.AssignmentAccessDeniedException;
import com.example.epari.global.exception.assignment.AssignmentInvalidException;
import com.example.epari.global.exception.assignment.AssignmentNotFoundException;
import com.example.epari.global.exception.auth.AuthUserNotFoundException;
import com.example.epari.global.exception.auth.InstructorNotFoundException;
import com.example.epari.global.exception.course.CourseInstructorMismatchException;
import com.example.epari.global.exception.file.AssignmentFileNotFoundException;
import com.example.epari.global.exception.file.FileDeleteFailedException;
import com.example.epari.global.exception.file.FileUploadFailedException;
import com.example.epari.user.domain.Instructor;
import com.example.epari.user.repository.InstructorRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.FileNotFoundException;
import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -42,24 +53,24 @@ public class AssignmentService {

private final S3FileService s3FileService;

private final AssignmentFileRepository assignmentFileRepository;
private final SubmissionRepository submissionRepository;

/**
* 과제 추가
*/
@Transactional
public AssignmentResponseDto addAssignment(Long courseId, AssignmentRequestDto requestDto, String email) {
BaseUser user = baseUserRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
.orElseThrow(AuthUserNotFoundException::new);

Course course = courseRepository.findById(courseId)
.orElseThrow(() -> new IllegalArgumentException("강의를 찾을 수 없습니다."));
.orElseThrow(CourseNotFoundException::new);

Instructor instructor = instructorRepository.findById(user.getId())
.orElseThrow(() -> new IllegalArgumentException("강사 정보를 찾을 수 없습니다."));
.orElseThrow(InstructorNotFoundException::new);

if (!course.getInstructor().getId().equals(instructor.getId())) {
throw new IllegalArgumentException("해당 강의의 담당 강사가 아닙니다.");
throw new CourseInstructorMismatchException();
}

Assignment assignment = Assignment.createAssignment(
Expand Down Expand Up @@ -110,11 +121,11 @@ public List<AssignmentResponseDto> getAssignmentsByTitle(String title) {
*/
public AssignmentResponseDto getAssignmentById(Long courseId, Long assignmentId) {
Assignment assignment = assignmentRepository.findByIdWithInstructor(assignmentId)
.orElseThrow(() -> new IllegalArgumentException("과제를 찾을 수 없습니다."));
.orElseThrow(AssignmentNotFoundException::new);

// 해당 과제가 요청된 코스에 속하는지 확인
if (!assignment.getCourse().getId().equals(courseId)) {
throw new IllegalArgumentException("해당 강의의 과제가 아닙니다.");
throw new AssignmentInvalidException();
}

return AssignmentResponseDto.from(assignment);
Expand All @@ -125,21 +136,21 @@ public AssignmentResponseDto getAssignmentById(Long courseId, Long assignmentId)
*/
@Transactional
public AssignmentResponseDto updateAssignment(Long courseId, Long assignmentId, AssignmentRequestDto requestDto,
String email) {
String email) {
BaseUser user = baseUserRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
.orElseThrow(AuthUserNotFoundException::new);

Course course = courseRepository.findById(courseId)
.orElseThrow(() -> new IllegalArgumentException("강의를 찾을 수 없습니다."));
.orElseThrow(CourseNotFoundException::new);

Assignment assignment = assignmentRepository.findByIdWithInstructor(assignmentId)
.orElseThrow(() -> new IllegalArgumentException("과제를 찾을 수 없습니다."));
.orElseThrow(AssignmentNotFoundException::new);

Instructor instructor = instructorRepository.findById(user.getId())
.orElseThrow(() -> new IllegalArgumentException("강사 정보를 찾을 수 없습니다."));
.orElseThrow(InstructorNotFoundException::new);

if (!course.getInstructor().getId().equals(instructor.getId())) {
throw new IllegalArgumentException("해당 강의의 담당 강사가 아닙니다.");
throw new CourseInstructorMismatchException();
}

assignment.updateAssignment(
Expand Down Expand Up @@ -171,20 +182,35 @@ public AssignmentResponseDto updateAssignment(Long courseId, Long assignmentId,
@Transactional
public void deleteAssignment(Long assignmentId, String email) {
BaseUser user = baseUserRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
.orElseThrow(AuthUserNotFoundException::new);

Assignment assignment = assignmentRepository.findByIdWithInstructor(assignmentId)
.orElseThrow(() -> new IllegalArgumentException("과제를 찾을 수 없습니다."));
.orElseThrow(AssignmentNotFoundException::new);

if (!assignment.getInstructor().getId().equals(user.getId())) {
throw new IllegalArgumentException("해당 과제의 삭제 권한이 없습니다.");
throw new AssignmentAccessDeniedException();
}

// 연관된 제출물의 S3 파일들 삭제
List<Submission> submissions = submissionRepository.findByAssignmentId(assignmentId);
for (Submission submission : submissions) {
for (SubmissionFile submissionFile : submission.getFiles()) {
try {
s3FileService.deleteFile(submissionFile.getFileUrl());
} catch (Exception e) {
log.error("S3에서 제출물 파일 삭제를 실패했습니다: {}", submissionFile.getFileUrl(), e);
throw new FileDeleteFailedException();
}
}
}

// 과제 S3 파일들 삭제
for (AssignmentFile assignmentFile : assignment.getFiles()) {
try {
s3FileService.deleteFile(assignmentFile.getFileUrl());
} catch (Exception e) {
log.error("S3에서 파일 삭제를 실패했습니다.", assignmentFile.getFileUrl(), e);
throw new FileDeleteFailedException();
}
}

Expand All @@ -197,13 +223,13 @@ public void deleteAssignment(Long assignmentId, String email) {
public String downloadFile(Long courseId, Long assignmentId, Long fileId) {
// 과제 확인
Assignment assignment = assignmentRepository.findByIdAndCourseId(assignmentId, courseId)
.orElseThrow(() -> new IllegalArgumentException("과제 자료를 찾을 수 없습니다."));
.orElseThrow(AssignmentNotFoundException::new);

// 파일 확인
AssignmentFile assignmentFile = assignment.getFiles().stream()
.filter(f -> f.getId().equals(fileId))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("파일을 찾을 수 없습니다."));
.orElseThrow(AssignmentFileNotFoundException::new);

// 7일간 유효한 다운로드 링크
return s3FileService.generatePresignedUrl(assignmentFile.getFileUrl(), Duration.ofDays(7));
Expand All @@ -215,20 +241,21 @@ public String downloadFile(Long courseId, Long assignmentId, Long fileId) {
@Transactional
public AssignmentResponseDto deleteFile(Long courseId, Long assignmentId, Long fileId) {
Assignment assignment = assignmentRepository.findByIdAndCourseId(assignmentId, courseId)
.orElseThrow(() -> new IllegalArgumentException("과제 파일을 찾을 수 없습니다."));
.orElseThrow(AssignmentNotFoundException::new);

AssignmentFile file = assignment.getFiles().stream()
.filter(f -> f.getId().equals(fileId))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("파일을 찾을 수 없습니다."));
.orElseThrow(AssignmentFileNotFoundException::new);

try {
s3FileService.deleteFile(file.getFileUrl());
} catch (Exception e) {
log.error("S3에서 파일 삭제를 실패했습니다: {}", file.getFileUrl(), e);
throw new FileDeleteFailedException();
}

//과제에서 파일 제거
// 과제에서 파일 제거
assignment.removeFile(file);

return AssignmentResponseDto.from(assignment);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.epari.assignment.service;

import com.example.epari.admin.exception.CourseNotFoundException;
import com.example.epari.assignment.domain.Assignment;
import com.example.epari.assignment.domain.Submission;
import com.example.epari.assignment.domain.SubmissionFile;
Expand All @@ -11,6 +12,11 @@
import com.example.epari.course.repository.CourseRepository;
import com.example.epari.global.common.enums.SubmissionGrade;
import com.example.epari.global.common.service.S3FileService;
import com.example.epari.global.exception.BusinessBaseException;
import com.example.epari.global.exception.ErrorCode;
import com.example.epari.global.exception.assignment.*;
import com.example.epari.global.exception.file.FileDeleteFailedException;
import com.example.epari.global.exception.file.SubmissionFileNotFoundException;
import com.example.epari.user.domain.Student;
import com.example.epari.user.repository.StudentRepository;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -47,13 +53,13 @@ public class SubmissionService {
public SubmissionResponseDto addSubmission(Long courseId, Long assignmentId, SubmissionRequestDto requestDto,
Long studentId) {
Course course = courseRepository.findById(courseId)
.orElseThrow(() -> new IllegalArgumentException("강의를 찾을 수 없습니다."));
.orElseThrow(CourseNotFoundException::new);

Assignment assignment = assignmentRepository.findById(assignmentId)
.orElseThrow(() -> new IllegalArgumentException("과제를 찾을 수 없습니다."));
.orElseThrow(AssignmentNotFoundException::new);

Student student = studentRepository.findById(studentId)
.orElseThrow(() -> new IllegalArgumentException("학생 정보를 찾을 수 없습니다."));
.orElseThrow(() -> new BusinessBaseException(ErrorCode.STUDENT_NOT_FOUND));

// 기존 제출물이 있는지 확인
Optional<Submission> existingSubmission = submissionRepository
Expand Down Expand Up @@ -96,14 +102,14 @@ public SubmissionResponseDto addSubmission(Long courseId, Long assignmentId, Sub
*/
public SubmissionResponseDto getSubmissionById(Long courseId, Long assignmentId, Long submissionId) {
Submission submission = submissionRepository.findById(submissionId)
.orElseThrow(() -> new IllegalArgumentException("제출된 과제를 찾을 수 없습니다."));
.orElseThrow(SubmissionNotFoundException::new);

// 제출된 과제가 해당 코스와 과제에 속하는지 확인
if (!submission.getAssignment().getId().equals(assignmentId) || !submission.getAssignment()
.getCourse()
.getId()
.equals(courseId)) {
throw new IllegalArgumentException("해당 과제의 제출물이 아닙니다.");
throw new SubmissionInvalidException();
}

return SubmissionResponseDto.from(submission);
Expand All @@ -123,7 +129,7 @@ public SubmissionResponseDto getStudentSubmission(Long courseId, Long assignment
Submission foundSubmission = submission.get();
// 제출된 과제가 해당 코스에 속하는지 확인
if (!foundSubmission.getAssignment().getCourse().getId().equals(courseId)) {
throw new IllegalArgumentException("해당 과제의 제출물이 아닙니다.");
throw new SubmissionInvalidException();
}

return SubmissionResponseDto.from(foundSubmission);
Expand All @@ -145,7 +151,7 @@ public List<SubmissionResponseDto> getSubmissionsByAssignmentId(Long assignmentI
public List<SubmissionResponseDto> getSubmissionsByCourse(Long courseId) {
// 강의 존재 여부 확인
Course course = courseRepository.findById(courseId)
.orElseThrow(() -> new IllegalArgumentException("강의를 찾을 수 없습니다."));
.orElseThrow(CourseNotFoundException::new);

List<Submission> submissions = submissionRepository.findByCourseId(courseId);
return submissions.stream()
Expand All @@ -160,11 +166,11 @@ public List<SubmissionResponseDto> getSubmissionsByCourse(Long courseId) {
public SubmissionResponseDto updateSubmission(Long courseId, Long assignmentId, Long submissionId,
SubmissionRequestDto requestDto, Long studentId) {
Submission submission = submissionRepository.findById(submissionId)
.orElseThrow(() -> new IllegalArgumentException("제출된 과제를 찾을 수 없습니다."));
.orElseThrow(SubmissionNotFoundException::new);

// 수정 권한 검증
if (!submission.getStudent().getId().equals(studentId)) {
throw new IllegalArgumentException("해당 과제의 수정 권한이 없습니다.");
throw new SubmissionStudentMismatchException();
}

submission.updateSubmission(requestDto.getDescription());
Expand All @@ -190,7 +196,7 @@ public SubmissionResponseDto updateSubmission(Long courseId, Long assignmentId,
@Transactional
public SubmissionResponseDto gradeSubmission(Long submissionId, SubmissionGrade grade, String feedback) {
Submission submission = submissionRepository.findById(submissionId)
.orElseThrow(() -> new IllegalArgumentException("제출된 과제를 찾을 수 없습니다."));
.orElseThrow(SubmissionNotFoundException::new);

submission.updateGrade(grade, feedback);

Expand All @@ -203,17 +209,17 @@ public SubmissionResponseDto gradeSubmission(Long submissionId, SubmissionGrade
@Transactional
public void deleteSubmission(Long courseId, Long assignmentId, Long submissionId, Long studentId) {
Submission submission = submissionRepository.findById(submissionId)
.orElseThrow(() -> new IllegalArgumentException("제출된 과제를 찾을 수 없습니다."));
.orElseThrow(SubmissionNotFoundException::new);

// 삭제 권한 검증
if (!submission.getStudent().getId().equals(studentId)) {
throw new IllegalArgumentException("해당 과제의 삭제 권한이 없습니다.");
throw new SubmissionAccessDeniedException();
}

// 과제가 해당 코스와 과제에 속하는지 확인
if (!submission.getAssignment().getId().equals(assignmentId) ||
!submission.getAssignment().getCourse().getId().equals(courseId)) {
throw new IllegalArgumentException("해당 과제의 제출물이 아닙니다.");
throw new SubmissionInvalidException();
}

// S3에서 관련 파일들 삭제
Expand All @@ -222,6 +228,7 @@ public void deleteSubmission(Long courseId, Long assignmentId, Long submissionId
s3FileService.deleteFile(submissionFile.getFileUrl());
} catch (Exception e) {
log.error("S3에서 파일 삭제를 실패했습니다: {}", submissionFile.getFileUrl(), e);
throw new FileDeleteFailedException();
}
}

Expand All @@ -234,11 +241,11 @@ public void deleteSubmission(Long courseId, Long assignmentId, Long submissionId
public List<SubmissionResponseDto> getSubmissionsWithStudents(Long courseId, Long assignmentId) {
// 과제 정보 조회
Assignment assignment = assignmentRepository.findById(assignmentId)
.orElseThrow(() -> new IllegalArgumentException("과제를 찾을 수 없습니다."));
.orElseThrow(AssignmentNotFoundException::new);

// 과제가 해당 코스의 것인지 확인
if (!assignment.getCourse().getId().equals(courseId)) {
throw new IllegalArgumentException("해당 코스의 과제가 아닙니다.");
throw new AssignmentInvalidException();
}

// 제출된 과제 목록 조회
Expand Down Expand Up @@ -267,13 +274,13 @@ public List<SubmissionResponseDto> getSubmissionsWithStudents(Long courseId, Lon
*/
public String downloadFile(Long courseId, Long assignmentId, Long submissionId, Long fileId) {
Submission submission = submissionRepository.findById(submissionId)
.orElseThrow(() -> new IllegalArgumentException("제출된 과제를 찾을 수 없습니다."));
.orElseThrow(SubmissionNotFoundException::new);

SubmissionFile submissionFile = submission.getFiles()
.stream()
.filter(f -> f.getId().equals(fileId))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("파일을 찾을 수 없습니다."));
.orElseThrow(SubmissionFileNotFoundException::new);

return s3FileService.generatePresignedUrl(submissionFile.getFileUrl(), Duration.ofDays(7));
}
Expand All @@ -284,17 +291,18 @@ public String downloadFile(Long courseId, Long assignmentId, Long submissionId,
@Transactional
public SubmissionResponseDto deleteFile(Long courseId, Long assignmentId, Long submissionId, Long fileId) {
Submission submission = submissionRepository.findById(submissionId)
.orElseThrow(() -> new IllegalArgumentException("제출된 과제를 찾을 수 없습니다."));
.orElseThrow(SubmissionNotFoundException::new);

SubmissionFile file = submission.getFiles().stream()
.filter(f -> f.getId().equals(fileId))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("파일을 찾을 수 없습니다."));
.orElseThrow(SubmissionFileNotFoundException::new);

try {
s3FileService.deleteFile(file.getFileUrl());
} catch (Exception e) {
log.error("S3에서 파일 삭제를 실패했습니다: {}", file.getFileUrl(), e);
throw new FileDeleteFailedException();
}

submission.removeFile(file);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ public enum ErrorCode {
// Assignment 관련 에러 코드 (ASM)
ASSIGNMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "ASM-001", "과제를 찾을 수 없습니다."),
SUBMISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "ASM-002", "과제 제출물을 찾을 수 없습니다."),
UNAUTHORIZED_ASSIGNMENT_ACCESS(HttpStatus.FORBIDDEN, "ASM-003", "과제에 대한 접근 권한이 없습니다."),
UNAUTHORIZED_SUBMISSION_ACCESS(HttpStatus.FORBIDDEN,"ASM-004","과제 제출물에 대한 접근 권한이 없습니다."),
ASSIGNMENT_INSTRUCTOR_MISMATCH(HttpStatus.FORBIDDEN, "ASM-005", "해당 과제에 대한 접근 권한이 없습니다."),
SUBMISSION_STUDENT_MISMATCH(HttpStatus.FORBIDDEN, "ASM-006", "해당 과제 제출물에 대한 접근 권한이 없습니다."),
ASSIGNMENT_INVALID(HttpStatus.BAD_REQUEST,"ASM-007","해당 강의의 과제가 아닙니다."),
SUBMISSION_INVALID(HttpStatus.BAD_REQUEST,"ASM-008","해당 과제의 제출물이 아닙니다."),


// Question 관련 에러 코드(EXAM)
QUESTION_NOT_FOUND(HttpStatus.NOT_FOUND, "QST-001", "문제를 찾을 수 없습니다."),
Expand Down
Loading