diff --git a/.DS_Store b/.DS_Store index 3494b3ee..e64cd66e 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/build.gradle b/build.gradle index aa589e3a..f7031ea0 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-actuator' testImplementation 'io.projectreactor:reactor-test' annotationProcessor 'org.projectlombok:lombok:1.18.30' @@ -62,6 +63,11 @@ dependencies { testImplementation 'org.mockito:mockito-junit-jupiter' implementation 'me.paulschwarz:spring-dotenv:4.0.0' + + //AWS S3 +// implementation 'software.amazon.awssdk:s3' + implementation 'software.amazon.awssdk:s3:2.32.1' + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } ext { diff --git a/postgres-data/base/1/1259 b/postgres-data/base/1/1259 deleted file mode 100644 index 18ecbb35..00000000 Binary files a/postgres-data/base/1/1259 and /dev/null differ diff --git a/postgres-data/base/1/2619 b/postgres-data/base/1/2619 deleted file mode 100644 index 935d6c44..00000000 Binary files a/postgres-data/base/1/2619 and /dev/null differ diff --git a/postgres-data/base/1/2619_fsm b/postgres-data/base/1/2619_fsm deleted file mode 100644 index 047f30ec..00000000 Binary files a/postgres-data/base/1/2619_fsm and /dev/null differ diff --git a/postgres-data/base/1/2619_vm b/postgres-data/base/1/2619_vm deleted file mode 100644 index 3e274250..00000000 Binary files a/postgres-data/base/1/2619_vm and /dev/null differ diff --git a/postgres-data/base/1/2696 b/postgres-data/base/1/2696 deleted file mode 100644 index e58541dd..00000000 Binary files a/postgres-data/base/1/2696 and /dev/null differ diff --git a/postgres-data/base/1/2840 b/postgres-data/base/1/2840 deleted file mode 100644 index 263d9bb1..00000000 Binary files a/postgres-data/base/1/2840 and /dev/null differ diff --git a/postgres-data/base/1/2841 b/postgres-data/base/1/2841 deleted file mode 100644 index 25cd99c7..00000000 Binary files a/postgres-data/base/1/2841 and /dev/null differ diff --git a/postgres-data/base/1/pg_internal.init b/postgres-data/base/1/pg_internal.init deleted file mode 100644 index 0b3ec0a6..00000000 Binary files a/postgres-data/base/1/pg_internal.init and /dev/null differ diff --git a/postgres-data/base/16384/1247 b/postgres-data/base/16384/1247 deleted file mode 100644 index 60f64f97..00000000 Binary files a/postgres-data/base/16384/1247 and /dev/null differ diff --git a/postgres-data/base/16384/1247_vm b/postgres-data/base/16384/1247_vm deleted file mode 100644 index 4a54348e..00000000 Binary files a/postgres-data/base/16384/1247_vm and /dev/null differ diff --git a/postgres-data/base/16384/1249 b/postgres-data/base/16384/1249 deleted file mode 100644 index 94cbdf52..00000000 Binary files a/postgres-data/base/16384/1249 and /dev/null differ diff --git a/postgres-data/base/16384/1249_fsm b/postgres-data/base/16384/1249_fsm deleted file mode 100644 index c92299d3..00000000 Binary files a/postgres-data/base/16384/1249_fsm and /dev/null differ diff --git a/postgres-data/base/16384/1249_vm b/postgres-data/base/16384/1249_vm deleted file mode 100644 index 5990046f..00000000 Binary files a/postgres-data/base/16384/1249_vm and /dev/null differ diff --git a/postgres-data/base/16384/1259 b/postgres-data/base/16384/1259 deleted file mode 100644 index f2281636..00000000 Binary files a/postgres-data/base/16384/1259 and /dev/null differ diff --git a/postgres-data/base/16384/1259_vm b/postgres-data/base/16384/1259_vm deleted file mode 100644 index 35325521..00000000 Binary files a/postgres-data/base/16384/1259_vm and /dev/null differ diff --git a/postgres-data/base/16384/2224 b/postgres-data/base/16384/2224 deleted file mode 100644 index 42acd7fe..00000000 Binary files a/postgres-data/base/16384/2224 and /dev/null differ diff --git a/postgres-data/base/16384/2579 b/postgres-data/base/16384/2579 deleted file mode 100644 index c00141f9..00000000 Binary files a/postgres-data/base/16384/2579 and /dev/null differ diff --git a/postgres-data/base/16384/2604 b/postgres-data/base/16384/2604 deleted file mode 100644 index c421e167..00000000 Binary files a/postgres-data/base/16384/2604 and /dev/null differ diff --git a/postgres-data/base/16384/2606 b/postgres-data/base/16384/2606 deleted file mode 100644 index b9fdf90e..00000000 Binary files a/postgres-data/base/16384/2606 and /dev/null differ diff --git a/postgres-data/base/16384/2606_vm b/postgres-data/base/16384/2606_vm deleted file mode 100644 index 16e4dd2a..00000000 Binary files a/postgres-data/base/16384/2606_vm and /dev/null differ diff --git a/postgres-data/base/16384/2608 b/postgres-data/base/16384/2608 deleted file mode 100644 index 5adf52ea..00000000 Binary files a/postgres-data/base/16384/2608 and /dev/null differ diff --git a/postgres-data/base/16384/2608_fsm b/postgres-data/base/16384/2608_fsm deleted file mode 100644 index 138107e7..00000000 Binary files a/postgres-data/base/16384/2608_fsm and /dev/null differ diff --git a/postgres-data/base/16384/2608_vm b/postgres-data/base/16384/2608_vm deleted file mode 100644 index 334d2068..00000000 Binary files a/postgres-data/base/16384/2608_vm and /dev/null differ diff --git a/postgres-data/base/16384/2610 b/postgres-data/base/16384/2610 deleted file mode 100644 index cc479bca..00000000 Binary files a/postgres-data/base/16384/2610 and /dev/null differ diff --git a/postgres-data/base/16384/2610_vm b/postgres-data/base/16384/2610_vm deleted file mode 100644 index a78c5af6..00000000 Binary files a/postgres-data/base/16384/2610_vm and /dev/null differ diff --git a/postgres-data/base/16384/2656 b/postgres-data/base/16384/2656 deleted file mode 100644 index 31063239..00000000 Binary files a/postgres-data/base/16384/2656 and /dev/null differ diff --git a/postgres-data/base/16384/2657 b/postgres-data/base/16384/2657 deleted file mode 100644 index 0302be42..00000000 Binary files a/postgres-data/base/16384/2657 and /dev/null differ diff --git a/postgres-data/base/16384/2658 b/postgres-data/base/16384/2658 deleted file mode 100644 index e90ed262..00000000 Binary files a/postgres-data/base/16384/2658 and /dev/null differ diff --git a/postgres-data/base/16384/2659 b/postgres-data/base/16384/2659 deleted file mode 100644 index c99d1d3a..00000000 Binary files a/postgres-data/base/16384/2659 and /dev/null differ diff --git a/postgres-data/base/16384/2662 b/postgres-data/base/16384/2662 deleted file mode 100644 index 6e41c751..00000000 Binary files a/postgres-data/base/16384/2662 and /dev/null differ diff --git a/postgres-data/base/16384/2663 b/postgres-data/base/16384/2663 deleted file mode 100644 index 4b0acaaf..00000000 Binary files a/postgres-data/base/16384/2663 and /dev/null differ diff --git a/postgres-data/base/16384/2664 b/postgres-data/base/16384/2664 deleted file mode 100644 index ff6e7e00..00000000 Binary files a/postgres-data/base/16384/2664 and /dev/null differ diff --git a/postgres-data/base/16384/2665 b/postgres-data/base/16384/2665 deleted file mode 100644 index 81bcdb22..00000000 Binary files a/postgres-data/base/16384/2665 and /dev/null differ diff --git a/postgres-data/base/16384/2666 b/postgres-data/base/16384/2666 deleted file mode 100644 index bb4f687e..00000000 Binary files a/postgres-data/base/16384/2666 and /dev/null differ diff --git a/postgres-data/base/16384/2667 b/postgres-data/base/16384/2667 deleted file mode 100644 index 85b8c34d..00000000 Binary files a/postgres-data/base/16384/2667 and /dev/null differ diff --git a/postgres-data/base/16384/2673 b/postgres-data/base/16384/2673 deleted file mode 100644 index f4562436..00000000 Binary files a/postgres-data/base/16384/2673 and /dev/null differ diff --git a/postgres-data/base/16384/2674 b/postgres-data/base/16384/2674 deleted file mode 100644 index a4d3f3ab..00000000 Binary files a/postgres-data/base/16384/2674 and /dev/null differ diff --git a/postgres-data/base/16384/2678 b/postgres-data/base/16384/2678 deleted file mode 100644 index a13c8b8e..00000000 Binary files a/postgres-data/base/16384/2678 and /dev/null differ diff --git a/postgres-data/base/16384/2679 b/postgres-data/base/16384/2679 deleted file mode 100644 index 75beb5a9..00000000 Binary files a/postgres-data/base/16384/2679 and /dev/null differ diff --git a/postgres-data/base/16384/2703 b/postgres-data/base/16384/2703 deleted file mode 100644 index 7c959ad5..00000000 Binary files a/postgres-data/base/16384/2703 and /dev/null differ diff --git a/postgres-data/base/16384/2704 b/postgres-data/base/16384/2704 deleted file mode 100644 index 037c3772..00000000 Binary files a/postgres-data/base/16384/2704 and /dev/null differ diff --git a/postgres-data/base/16384/3455 b/postgres-data/base/16384/3455 deleted file mode 100644 index 588d4d9d..00000000 Binary files a/postgres-data/base/16384/3455 and /dev/null differ diff --git a/postgres-data/base/16384/5002 b/postgres-data/base/16384/5002 deleted file mode 100644 index 5df99a72..00000000 Binary files a/postgres-data/base/16384/5002 and /dev/null differ diff --git a/postgres-data/base/5/pg_internal.init b/postgres-data/base/5/pg_internal.init deleted file mode 100644 index f8019d41..00000000 Binary files a/postgres-data/base/5/pg_internal.init and /dev/null differ diff --git a/postgres-data/global/1260 b/postgres-data/global/1260 deleted file mode 100644 index 357a0613..00000000 Binary files a/postgres-data/global/1260 and /dev/null differ diff --git a/postgres-data/global/pg_control b/postgres-data/global/pg_control deleted file mode 100644 index d5cfcb45..00000000 Binary files a/postgres-data/global/pg_control and /dev/null differ diff --git a/postgres-data/global/pg_internal.init b/postgres-data/global/pg_internal.init deleted file mode 100644 index ae86f8ab..00000000 Binary files a/postgres-data/global/pg_internal.init and /dev/null differ diff --git a/postgres-data/pg_wal/000000010000000000000001 b/postgres-data/pg_wal/000000010000000000000001 deleted file mode 100644 index 09624f6e..00000000 Binary files a/postgres-data/pg_wal/000000010000000000000001 and /dev/null differ diff --git a/postgres-data/pg_xact/0000 b/postgres-data/pg_xact/0000 deleted file mode 100644 index 7a9016ee..00000000 Binary files a/postgres-data/pg_xact/0000 and /dev/null differ diff --git a/src/main/.DS_Store b/src/main/.DS_Store index fa944d0c..20ebdeb2 100644 Binary files a/src/main/.DS_Store and b/src/main/.DS_Store differ diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java b/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java new file mode 100644 index 00000000..55543cfd --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java @@ -0,0 +1,41 @@ +package hello.cluebackend.domain.assignment.domain; + +import hello.cluebackend.domain.assignment.presentation.dto.AssignmentAttachmentDto; +import hello.cluebackend.domain.classroom.domain.ClassRoom; +import hello.cluebackend.domain.user.domain.UserEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "assignments") +@Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class Assignment { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "assignment_id") + private Long assignmentId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "class_room_id") + private ClassRoom classRoom; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "creator_id") + private UserEntity user; + + @Column(name = "title") + private String title; + + @Column(name = "content") + private String content; + + @Column(name = "start_date") + private LocalDateTime startDate; + + @Column(name = "due_date") + private LocalDateTime dueDate; +} \ 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 new file mode 100644 index 00000000..10bfb82b --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java @@ -0,0 +1,60 @@ +package hello.cluebackend.domain.assignment.domain; + +import hello.cluebackend.domain.assignment.presentation.dto.AssignmentAttachmentDto; +import hello.cluebackend.domain.user.domain.UserEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "assignment_attachment") +@Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class AssignmentAttachment { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long assignmentAttachmentId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="assignment_id") + private Assignment assignment; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private UserEntity user; + + @Column(name = "original_file_name") + private String originalFileName; + + @Column(name = "stored_file_name") + private String storedFileName; + + @Column(name = "file_path") + private String filePath; + + @Column(name = "file_size") + private Integer fileSize; + + @Enumerated(EnumType.STRING) + @Column(name = "submit_type") + private SubmitType submitType; + + @Column(name = "update_date") + private LocalDateTime updateDate; + + public AssignmentAttachmentDto toDto() { + return AssignmentAttachmentDto.builder() + .assignmentAttachmentId(assignmentAttachmentId) + .assignment(assignment) + .user(user) + .originalFileName(originalFileName) + .storedFileName(storedFileName) + .filePath(filePath) + .fileSize(fileSize) + .submitType(submitType) + .updateDate(updateDate) + .build(); + } +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java new file mode 100644 index 00000000..b83ee612 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java @@ -0,0 +1,32 @@ +package hello.cluebackend.domain.assignment.domain; + +import hello.cluebackend.domain.user.domain.UserEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name="assignment_check") +@Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class AssignmentCheck { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long assignmentCheckId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private UserEntity user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "assignment_id") + private Assignment assignment; + + @Column(name="is_submitted") + private Boolean isSubmitted; + + @Column(name="submitted_at") + private LocalDateTime submittedAt; +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentContent.java b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentContent.java new file mode 100644 index 00000000..e85b0357 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentContent.java @@ -0,0 +1,46 @@ +package hello.cluebackend.domain.assignment.domain; + +import hello.cluebackend.domain.user.domain.UserEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "assignment_content") +@Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class AssignmentContent { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="assignment_content_id") + private Long assignmentContentId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="user_id") + private UserEntity user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="assignment_id") + private Assignment assignment; + + @JoinColumn(name="original_file_name") + private String originalFileName; + + @Column(name="stored_file_name") + private String storedFileName; + + @Column(name="file_path") + private String filePath; + + @Column(name="file_size") + private int fileSize; + + @Enumerated(EnumType.STRING) + @Column(name = "submit_type") + private SubmitType submitType; // 1. 랑크 , 2. 파일 + + @Column(name="update_date") + private LocalDateTime updateDate; +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/SubmitType.java b/src/main/java/hello/cluebackend/domain/assignment/domain/SubmitType.java new file mode 100644 index 00000000..285d5015 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/SubmitType.java @@ -0,0 +1,7 @@ +package hello.cluebackend.domain.assignment.domain; + +public enum SubmitType { + FILE, + LINK, + IMAGE +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentAttachmentRepository.java b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentAttachmentRepository.java new file mode 100644 index 00000000..507ef691 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentAttachmentRepository.java @@ -0,0 +1,13 @@ +package hello.cluebackend.domain.assignment.domain.repository; + +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface AssignmentAttachmentRepository extends JpaRepository { + List findAllByAssignment(Assignment assignment); +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentCheckRepository.java b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentCheckRepository.java new file mode 100644 index 00000000..f6a74981 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentCheckRepository.java @@ -0,0 +1,24 @@ +package hello.cluebackend.domain.assignment.domain.repository; + +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.assignment.domain.AssignmentCheck; +import io.lettuce.core.dynamic.annotation.Param; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface AssignmentCheckRepository extends JpaRepository { + @Query(""" +SELECT ac.assignment +FROM AssignmentCheck ac +JOIN ac.assignment a +JOIN a.classRoom cr +JOIN cr.classRoomUserList cru +WHERE ac.user.userId = :userId + AND ac.isSubmitted = false +""") + List findUnsubmittedAssignmentsByUserId(@Param("userId") Long userId); +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentContentRepository.java b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentContentRepository.java new file mode 100644 index 00000000..74711fc7 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentContentRepository.java @@ -0,0 +1,7 @@ +package hello.cluebackend.domain.assignment.domain.repository; + +import hello.cluebackend.domain.assignment.domain.AssignmentContent; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AssignmentContentRepository extends JpaRepository { +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentRepository.java b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentRepository.java new file mode 100644 index 00000000..90da6c70 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentRepository.java @@ -0,0 +1,10 @@ +package hello.cluebackend.domain.assignment.domain.repository; + +import hello.cluebackend.domain.assignment.domain.Assignment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface AssignmentRepository extends JpaRepository { + List findAllByClassRoom_ClassRoomId(Long classId); +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/exception/AssignmentNotFoundException.java b/src/main/java/hello/cluebackend/domain/assignment/exception/AssignmentNotFoundException.java new file mode 100644 index 00000000..48ed0c42 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/exception/AssignmentNotFoundException.java @@ -0,0 +1,15 @@ +package hello.cluebackend.domain.assignment.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) // 404 Not Found 응답 +public class AssignmentNotFoundException extends RuntimeException { + public AssignmentNotFoundException() { + super("해당 과제를 찾을 수 없습니다."); + } + + public AssignmentNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/exception/UnauthorizedException.java b/src/main/java/hello/cluebackend/domain/assignment/exception/UnauthorizedException.java new file mode 100644 index 00000000..eb04b19a --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/exception/UnauthorizedException.java @@ -0,0 +1,15 @@ +package hello.cluebackend.domain.assignment.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.FORBIDDEN) // 403 Forbidden 응답 +public class UnauthorizedException extends RuntimeException { + public UnauthorizedException() { + super("접근 권한이 없습니다."); + } + + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java new file mode 100644 index 00000000..c06e14e4 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java @@ -0,0 +1,168 @@ +package hello.cluebackend.domain.assignment.presentation; + + +import hello.cluebackend.domain.assignment.presentation.dto.AssignmentAttachmentDto; +import hello.cluebackend.domain.assignment.presentation.dto.request.AssignmentCreateRequestDto; +import hello.cluebackend.domain.assignment.presentation.dto.response.GetAssignmentResponseDto; +import hello.cluebackend.domain.assignment.presentation.dto.response.StudentAssignmentRemain; +import hello.cluebackend.domain.assignment.service.AssignmentService; +import hello.cluebackend.domain.assignment.service.FileService; +import hello.cluebackend.global.config.JWTUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.util.UriUtils; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/assignments") +public class AssignmentController { + private final AssignmentService assignmentService; + private final JWTUtil jWTUtil; + private final FileService fileService; + + private Long jwtTokenTaker(HttpServletRequest request){ + String token = jWTUtil.getToken(request); + Long userId = jWTUtil.getUserId(token); + return userId; + } + + // 교실 전체 과제 조회하기 + @GetMapping("/{classId}") + public ResponseEntity> getAllAssignment( + HttpServletRequest request, + @PathVariable Long classId + ){ + Long userId = jwtTokenTaker(request); + return ResponseEntity.ok(assignmentService.getAllAssignment(classId, userId)); + } + + // 과제 생성하기 + @PostMapping("/{classId}") + public ResponseEntity createAssignment( + HttpServletRequest request, + @PathVariable Long classId, + @RequestPart("metadata") AssignmentCreateRequestDto requestDto, + @RequestPart("files") List files + ){ + try { + Long userId = jwtTokenTaker(request); + assignmentService.createAssignment(userId, classId, requestDto, files); + return ResponseEntity.status(HttpStatus.CREATED).body("과제 생성 완료"); + } catch(Exception e) { + log.error(e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + } + + // 선생님 첨부 파일 다운 받기 + @GetMapping("/attachment/{attachmentId}") + public ResponseEntity downloadAttachment( + @PathVariable Long attachmentId, + HttpServletRequest request + ){ + Long userId = jwtTokenTaker(request); + + try { + Resource file = fileService.downloadFile(attachmentId, userId); + AssignmentAttachmentDto assignmentDto = assignmentService.findById(attachmentId); + String fullPath = assignmentDto.getFilePath(); + UrlResource resource = new UrlResource("file:" + fullPath); + + String encodedFileName = UriUtils.encode(fullPath.split("_")[1], StandardCharsets.UTF_8); + String contentDisposition = "attachment; filename=\"" + encodedFileName + "\""; + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) + .body(resource); + } catch(Exception e) { + log.error(e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + } +// // 선생님 첨부 파일 다운 받기 +// @GetMapping("/attachment/{attachmentId}") +// public ResponseEntity downloadAttachment( +// @PathVariable Long attachmentId, +// HttpServletRequest request +// ){ +// Long userId = jwtTokenTaker(request); +// +// try { +// Resource file = fileService.downloadFile(attachmentId, userId); +// +// return ResponseEntity.ok() +// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"downloaded-file\"") +// .contentType(MediaType.APPLICATION_OCTET_STREAM) +// .body(file); +// } catch(Exception e) { +// log.error(e.getMessage()); +// return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); +// } +// } + + + // 학생 메인 페이지 과제 남은 일수 + @GetMapping("/check") + public ResponseEntity> getStudentRemainCard ( + HttpServletRequest request + ){ + Long userId = jwtTokenTaker(request); + List remains = assignmentService.getUnsubmittedAssignments(userId); + return ResponseEntity.ok(remains); + } + + + +// +// @DeleteMapping("/{classId}") +// public ResponseEntity deleteAssignment( +// HttpServletRequest request, +// @PathVariable Long classId, +// @RequestParam("assignmentId") Long assignmentId +// ) { +// String token = jWTUtil.getToken(request); +// Long userId = jWTUtil.getUserId(token); +// +// try { +// assignmentService.deleteAssignment(classId, assignmentId, userId); +// return ResponseEntity.noContent().build(); +// } catch (AssignmentNotFoundException e) { +// return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); +// } catch (UnauthorizedException e) { +// return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null); +// } +// } +// +// @GetMapping("/{classId}") +// public ResponseEntity getAssignmentList( +// HttpServletRequest request, +// @PathVariable Long classId +// ) { +// String token = jWTUtil.getToken(request); +// Long userId = jWTUtil.getUserId(token); +// +// return ResponseEntity.ok(assignmentService.getAssignmentList(classId)); +// } + + +// @GetMapping("{classId}") +// public ResponseEntity> getAllAssignments( +// @PathVariable Long classId, +// @RequestParam Long userId +// ){ +// return ResponseEntity.ok(assignmentService.getAssignmentsForStudent(classId, userId)); +// } +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/AssignmentAttachmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/AssignmentAttachmentDto.java new file mode 100644 index 00000000..0114b0d6 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/AssignmentAttachmentDto.java @@ -0,0 +1,26 @@ +package hello.cluebackend.domain.assignment.presentation.dto; + +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.assignment.domain.SubmitType; +import hello.cluebackend.domain.user.domain.UserEntity; +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@ToString +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AssignmentAttachmentDto { + private Long assignmentAttachmentId; + private Assignment assignment; + private UserEntity user; + private String originalFileName; + private String storedFileName; + private String filePath; + private Integer fileSize; + private SubmitType submitType; + private LocalDateTime updateDate; +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentCreateRequestDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentCreateRequestDto.java new file mode 100644 index 00000000..9441d343 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentCreateRequestDto.java @@ -0,0 +1,14 @@ +package hello.cluebackend.domain.assignment.presentation.dto.request; + +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter @Setter +public class AssignmentCreateRequestDto { + private String title; + private String content; + private LocalDateTime startData; + private LocalDateTime dueDate; +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentSubmitStatusDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentSubmitStatusDto.java new file mode 100644 index 00000000..71c2b38a --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentSubmitStatusDto.java @@ -0,0 +1,12 @@ +package hello.cluebackend.domain.assignment.presentation.dto.request; + +import java.time.LocalDateTime; + +public record AssignmentSubmitStatusDto( + String userName, + boolean isSubmitted, + LocalDateTime submittedAt, + Long userId, + Long assignmentId +) { +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentDuration.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentDuration.java new file mode 100644 index 00000000..c8ccd5db --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentDuration.java @@ -0,0 +1,8 @@ +package hello.cluebackend.domain.assignment.presentation.dto.response; + +import java.time.LocalDateTime; + +public record AssignmentDuration( + LocalDateTime startDate, + LocalDateTime endDate +){} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/Assignmentfile.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/Assignmentfile.java new file mode 100644 index 00000000..214c782e --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/Assignmentfile.java @@ -0,0 +1,7 @@ +package hello.cluebackend.domain.assignment.presentation.dto.response; + +public record Assignmentfile( + Long fileId, + String fileName, + int fileSize +) {} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAssignmentResponseDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAssignmentResponseDto.java new file mode 100644 index 00000000..f417079b --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAssignmentResponseDto.java @@ -0,0 +1,12 @@ +package hello.cluebackend.domain.assignment.presentation.dto.response; + +import java.time.LocalDateTime; +import java.util.List; + +public record GetAssignmentResponseDto( + Long assignmentId, + String title, + LocalDateTime endDate, + String duringDate, + List files +) {} diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/StudentAssignmentRemain.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/StudentAssignmentRemain.java new file mode 100644 index 00000000..ce105dc1 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/StudentAssignmentRemain.java @@ -0,0 +1,7 @@ +package hello.cluebackend.domain.assignment.presentation.dto.response; + +public record StudentAssignmentRemain( + String title, + String duration, + Long assignmentId +) {} diff --git a/src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java b/src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java new file mode 100644 index 00000000..09ff87b3 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java @@ -0,0 +1,347 @@ +package hello.cluebackend.domain.assignment.service; + +import com.zaxxer.hikari.pool.HikariProxyCallableStatement; +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; +import hello.cluebackend.domain.assignment.domain.AssignmentCheck; +import hello.cluebackend.domain.assignment.domain.SubmitType; +import hello.cluebackend.domain.assignment.domain.repository.AssignmentAttachmentRepository; +import hello.cluebackend.domain.assignment.domain.repository.AssignmentContentRepository; +import hello.cluebackend.domain.assignment.domain.repository.AssignmentRepository; +import hello.cluebackend.domain.assignment.domain.repository.AssignmentCheckRepository; +import hello.cluebackend.domain.assignment.presentation.dto.AssignmentAttachmentDto; +import hello.cluebackend.domain.assignment.presentation.dto.request.AssignmentCreateRequestDto; +import hello.cluebackend.domain.assignment.presentation.dto.response.AssignmentDuration; +import hello.cluebackend.domain.assignment.presentation.dto.response.Assignmentfile; +import hello.cluebackend.domain.assignment.presentation.dto.response.GetAssignmentResponseDto; +import hello.cluebackend.domain.assignment.presentation.dto.response.StudentAssignmentRemain; +import hello.cluebackend.domain.classroom.domain.ClassRoom; +import hello.cluebackend.domain.classroom.domain.repository.ClassRoomRepository; +import hello.cluebackend.domain.classroomuser.domain.ClassRoomUser; +import hello.cluebackend.domain.classroomuser.domain.repository.ClassRoomUserRepository; +import hello.cluebackend.domain.document.presentation.dto.FileUpload; +import hello.cluebackend.domain.user.domain.Role; +import hello.cluebackend.domain.user.domain.UserEntity; +import hello.cluebackend.domain.user.domain.repository.UserRepository; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AssignmentService { + + @Value("${upload.local.dir}") + private String uploadDir; + + private final UserRepository userRepository; + private final ClassRoomUserRepository classRoomUserRepository; + private final AssignmentRepository assignmentRepository; + private final AssignmentAttachmentRepository assignmentAttachmentRepository; + private final AssignmentCheckRepository assignmentCheckRepository; + private final ClassRoomRepository classRoomRepository; + + private final FileService fileService; + + + private UserEntity validated(Long userId, Long classId){ + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException("해당 유저를 찾을수 없습니다.")); +// System.out.println("user = " + user); + if(!Role.TEACHER.equals(user.getRole())) { + throw new AccessDeniedException("권한이 부족한 사용자 입니다."); + } + + List classRoomUsers = classRoomUserRepository.findByUser_UserId(userId); + + boolean isMemberOfClass = classRoomUsers.stream() + .anyMatch(classRoomUser -> classRoomUser.getClassRoom().getClassRoomId().equals(classId)); + + if (!isMemberOfClass) { + throw new AccessDeniedException("해당 수업에 속하지 않은 사용자입니다."); + } + return user; + } + + private String calculateRemainingTime(LocalDateTime now, LocalDateTime dueDate) { + Duration duration = Duration.between(now, dueDate); + long days = duration.toDays(); + long hours = duration.minusDays(days).toHours(); + + if (duration.isNegative()) { + return "마감됨"; + } + + return days + "일 " + hours + "시간 남음"; + } + + public String generateUniqueFileName(String originalFileName) { + String extension = ""; + + int dotIndex = originalFileName.lastIndexOf("."); + if (dotIndex > 0) { + extension = originalFileName.substring(dotIndex); // 확장자 포함 + } + + String uuid = UUID.randomUUID().toString(); + + return uuid + extension; + } + + public List getAllAssignment(Long classId, Long userId) { + + UserEntity user = validated(classId, userId); + + List assignments = assignmentRepository.findAllByClassRoom_ClassRoomId(classId); + + return assignments.stream().map(assignment -> { + List attachments = assignmentAttachmentRepository.findAllByAssignment(assignment); + + List fileDtos = attachments.stream() + .map(attachment -> new Assignmentfile( + attachment.getAssignmentAttachmentId(), + attachment.getOriginalFileName(), + attachment.getFileSize() + )) + .toList(); + + String remainingTime = calculateRemainingTime(LocalDateTime.now(), assignment.getDueDate()); + + return new GetAssignmentResponseDto( + assignment.getAssignmentId(), + assignment.getTitle(), + assignment.getDueDate(), + remainingTime, + fileDtos + ); + }).toList(); + } + + @Transactional + public void createAssignment(Long userId, Long classId, AssignmentCreateRequestDto requestDto, List files) { + UserEntity user = validated(userId, classId); + ClassRoom classRoom = classRoomRepository.findById(classId) + .orElseThrow(() -> new EntityNotFoundException("해당 반을 찾을 수 없습니다.")); + + Assignment assignment = Assignment.builder() + .classRoom(classRoom) + .user(user) + .title(requestDto.getTitle()) + .content(requestDto.getContent()) + .startDate(requestDto.getStartData()) + .dueDate(requestDto.getDueDate()) + .build(); + + assignmentRepository.save(assignment); + + if (files != null && !files.isEmpty()) { + for (MultipartFile file : files) { +// String storedFileName = fileService.storeFile(file); + FileUpload uploadResult = upload(file); + AssignmentAttachment attachment = AssignmentAttachment.builder() + .assignment(assignment) + .user(user) + .originalFileName(file.getOriginalFilename()) + .storedFileName(uploadResult.getStoredFileName()) + .filePath(uploadResult.getFullPath()) + .fileSize((int) file.getSize()) + .submitType(SubmitType.FILE) + .updateDate(LocalDateTime.now()) + .build(); + + assignmentAttachmentRepository.save(attachment); + } + } + +// @Transactional +// public void createAssignment(Long userId, Long classId, AssignmentCreateRequestDto requestDto, List files) { +// UserEntity user = validated(userId, classId); +// ClassRoom classRoom = classRoomRepository.findById(classId) +// .orElseThrow(() -> new EntityNotFoundException("해당 반을 찾을 수 없습니다.")); +// +// Assignment assignment = Assignment.builder() +// .classRoom(classRoom) +// .user(user) +// .title(requestDto.getTitle()) +// .content(requestDto.getContent()) +// .startDate(requestDto.getStartData()) +// .dueDate(requestDto.getDueDate()) +// .build(); +// +// assignmentRepository.save(assignment); +// +// if (files != null && !files.isEmpty()) { +// for (MultipartFile file : files) { +// String storedFileName = fileService.storeFile(file); +// AssignmentAttachment attachment = AssignmentAttachment.builder() +// .assignment(assignment) +// .user(user) +// .originalFileName(file.getOriginalFilename()) +// .storedFileName(storedFileName) +//// .filePath("/uploads/" + storedFileName) +// .filePath(storedFileName) +// .fileSize((int) file.getSize()) +// .submitType(SubmitType.FILE) +// .updateDate(LocalDateTime.now()) +// .build(); +// +// assignmentAttachmentRepository.save(attachment); +// } +// } + + List students = classRoomUserRepository.findAllStudentsByClassRoomId(classId); + for (UserEntity student : students) { + AssignmentCheck check = AssignmentCheck.builder() + .assignment(assignment) + .user(student) + .isSubmitted(false) + .submittedAt(null) + .build(); + assignmentCheckRepository.save(check); + } + } + + public List getUnsubmittedAssignments(Long userId) { + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException("해당 학생을 찾을수 없습니다.")); + + List assignments = assignmentCheckRepository.findUnsubmittedAssignmentsByUserId(userId); + + return assignments.stream() + .map(a -> new StudentAssignmentRemain( + a.getTitle(), + new AssignmentDuration(a.getStartDate(), a.getDueDate()).toString(), + a.getAssignmentId() + )).collect(Collectors.toList()); + } + + public FileUpload upload(MultipartFile file) { + + String originalFileName = file.getOriginalFilename(); + String storedFileName = generateStoredFileName(originalFileName); + String fullPath = getFullPath(storedFileName); + File dest = new File(fullPath); + + if(!dest.getParentFile().exists()) { + boolean created = dest.getParentFile().mkdirs(); + if(!created) { + log.error("Unable to create directory {}", dest.getParentFile().getAbsolutePath()); + throw new RuntimeException("Directory creation failed"); + } + } + + try { + file.transferTo(dest); + } catch (IOException e) { + log.error("File uploading failed {}", originalFileName, e); + throw new RuntimeException("File uploading failed " + originalFileName, e); + } + log.info("File uploaded {}", originalFileName); + return FileUpload.builder() + .originalFileName(originalFileName) + .storedFileName(storedFileName) + .fullPath(fullPath) + .build(); + } + + // uuid_원본파일명 + private String generateStoredFileName(String originalFileName) { + return UUID.randomUUID().toString() + "_" + originalFileName; + } + + public String getFullPath(String fileName) { + return uploadDir + File.separator + fileName; + } + + public AssignmentAttachmentDto findById(Long attachmentId) { + AssignmentAttachment findAssignmentAttachment = assignmentAttachmentRepository.findById(attachmentId).orElseThrow(() -> new EntityNotFoundException("해당 과제가 없습니다.")); + return findAssignmentAttachment.toDto(); + } + +// public void createAssignment(Long userId, Long classId, AssignmentCreateRequestDto requestDto, MultipartFile file) { +// UserEntity user = userRepository.findById(userId) +// .orElseThrow(() -> new EntityNotFoundException("해당 사용자를 찾을 수 없습니다.")); +// +// if (user.getRole() != Role.TEACHER) { +// throw new AccessDeniedException("과제를 생성할 권한이 없습니다."); +// } +// +// Assignment assignment = Assignment.builder() +// .creator(user) +// .classRoom(ClassRoom.builder().classRoomId(classId).build()) +// .title(requestDto.getTitle()) +// .content(requestDto.getContent()) +// .startDate(requestDto.getStartData()) +// .dueDate(requestDto.getDueDate()) +// .build(); +// +// assignmentRepository.save(assignment); +// +// // 파일이 있는 경우에만 첨부 정보 저장 +// if (file != null && !file.isEmpty()) { +// // 실제 파일 저장 처리 (ex: S3, 로컬, etc) +// String storedFileName = generateUniqueFileName(file.getOriginalFilename()); +// String filePath = "TeacherAssignment/" + storedFileName; +// +// // 예: S3Uploader.upload(file, storedFileName) +// AssignmentAttachment attachment = AssignmentAttachment.builder() +// .user(user) +// .assignment(assignment) +// .originalFileName(file.getOriginalFilename()) +// .storedFileName(storedFileName) +// .filePath(filePath) +// .fileSize((int) file.getSize()) +// .submitType(2) // 예시: 2 = 일반 업로드, 1 = 링크 저장 +// .updateDate(LocalDateTime.now()) +// .build(); +// +// assignmentAttachmentRepository.save(attachment); +// } +// } + +// public void deleteAssignment(Long userId, Long assignmentId, Long classRoomId) { +// UserEntity user = userRepository.findById(userId) +// .orElseThrow(() -> new EntityNotFoundException("해당 사용자를 찾을 수 없습니다.")); +// +// if (user.getRole() != Role.TEACHER) { +// throw new UnauthorizedException("삭제 권한이 없습니다."); +// } +// +// Assignment assignment = assignmentRepository.findById(assignmentId) +// .orElseThrow(() -> new EntityNotFoundException("해당 과제를 찾을 수 없습니다.")); +// +// if (!assignment.getClassRoom().getClassRoomId().equals(classRoomId)) { +// throw new IllegalArgumentException("해당 과제는 이 반에 속하지 않습니다."); +// } +// +// assignmentRepository.delete(assignment); +// } +// +// +// +// public Object getSubmissionStatus(Long classId, Long assignmentId) { +// List checks = assignmentCheckRepository.findAllByAssignment_AssignmentId(assignmentId); +// +// return checks.stream().map(check -> new AssignmentSubmitStatusDto( +// check.getUser().getUsername(), +// check.getIsSubmitted(), +// check.getSubmittedAt(), +// check.getUser().getUserId(), +// check.getAssignment().getAssignmentId() +// )).toList(); +// } +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/service/FileService.java b/src/main/java/hello/cluebackend/domain/assignment/service/FileService.java new file mode 100644 index 00000000..040088ee --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/service/FileService.java @@ -0,0 +1,60 @@ +package hello.cluebackend.domain.assignment.service; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.amazonaws.services.s3.model.S3Object; +import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; +import hello.cluebackend.domain.assignment.domain.repository.AssignmentAttachmentRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class FileService { + private final AmazonS3Client amazonS3Client; + private final AssignmentAttachmentRepository assignmentAttachmentRepository; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public String storeFile(MultipartFile file) { + String originalFilename = file.getOriginalFilename(); + String extension = ""; + if (originalFilename != null && originalFilename.contains(".")) { + extension = originalFilename.substring(originalFilename.lastIndexOf(".")); + } + String storedFileName = UUID.randomUUID().toString() + extension; + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + metadata.setContentType(file.getContentType()); + + try { + amazonS3Client.putObject(new PutObjectRequest(bucket, storedFileName, file.getInputStream(), metadata)); // 공개 권한 설정 + +// return amazonS3Client.getUrl(bucket, storedFileName).toString(); + return storedFileName; + } catch (IOException e) { + throw new RuntimeException("파일 업로드에 실패했습니다.", e); + } + } + + public Resource downloadFile(Long attachmentId, Long userId) { + AssignmentAttachment attachment = assignmentAttachmentRepository.findById(attachmentId) + .orElseThrow(() -> new RuntimeException("파일을 찾을 수 없습니다.")); + + String filePath = attachment.getFilePath(); + + S3Object s3Object = amazonS3Client.getObject(bucket, filePath); + + return new InputStreamResource(s3Object.getObjectContent()); + } +} diff --git a/src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java b/src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java index 2bec64f1..a2d8b445 100644 --- a/src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java +++ b/src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java @@ -7,6 +7,7 @@ import hello.cluebackend.domain.user.presentation.dto.UserDto; import hello.cluebackend.domain.user.service.UserService; import hello.cluebackend.global.config.JWTUtil; +import jakarta.persistence.EntityNotFoundException; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -20,7 +21,6 @@ @RestController @RequestMapping("/api/class") public class ClassRoomController { - private final JWTUtil jwtUtil; private final ClassRoomService classRoomService; @@ -36,6 +36,15 @@ public ResponseEntity> getAllClassRooms(HttpServletReques return ResponseEntity.ok(classRoomService.findMyClassRoomById(userId)); } + @GetMapping("/{classId}/all") + public ResponseEntity getAllInfo(HttpServletRequest request, @PathVariable Long classId){ + String token = jwtUtil.getToken(request); +// Long userId = jwtUtil.getUserId(token); +// Role role = jwtUtil.getRole(token); + + return ResponseEntity.ok(classRoomService.getAllInfo(classId)); + } + @PostMapping public ResponseEntity> createClassRoom(@RequestBody ClassRoomDto classRoomDTO, HttpServletRequest request) { String token = jwtUtil.getToken(request); @@ -47,7 +56,8 @@ public ResponseEntity> createClassRoom(@RequestBody ClassRoomDto cl try { classRoomService.createClassRoom(classRoomDTO, userId); return new ResponseEntity<>(HttpStatus.OK); - } catch (RuntimeException e){ + } catch (EntityNotFoundException e){ + log.error(e.getMessage()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } } @@ -71,9 +81,9 @@ public ResponseEntity findClassRoom(@PathVariable Long classId, Ht String token = jwtUtil.getToken(request); Role role = jwtUtil.getRole(token); - if(role != Role.TEACHER) { - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); - } +// if(role != Role.TEACHER) { +// return new ResponseEntity<>(HttpStatus.BAD_REQUEST); +// } ClassRoomDto findClassRoomDto = classRoomService.findById(classId); return ResponseEntity.ok(findClassRoomDto); diff --git a/src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java b/src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java new file mode 100644 index 00000000..a267de75 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/classroom/presentation/dto/ClassRoomAllInfoDto.java @@ -0,0 +1,20 @@ +package hello.cluebackend.domain.classroom.presentation.dto; + +import hello.cluebackend.domain.directory.presentation.dto.DirectoryAllInfoDto; +import lombok.*; + +import java.util.List; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ClassRoomAllInfoDto { + + private Long classRoomId; + private String classRoomName; + private String description; + private List directoryList; + private List teacherNames; +} diff --git a/src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java b/src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java index 12eff798..cdb669ac 100644 --- a/src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java +++ b/src/main/java/hello/cluebackend/domain/classroom/service/ClassRoomService.java @@ -2,17 +2,27 @@ import hello.cluebackend.domain.classroom.domain.ClassRoom; import hello.cluebackend.domain.classroom.domain.repository.ClassRoomRepository; +import hello.cluebackend.domain.classroom.presentation.dto.ClassRoomAllInfoDto; import hello.cluebackend.domain.classroom.presentation.dto.ClassRoomCardDto; import hello.cluebackend.domain.classroom.presentation.dto.ClassRoomDto; import hello.cluebackend.domain.classroomuser.domain.ClassRoomUser; import hello.cluebackend.domain.classroomuser.domain.repository.ClassRoomUserRepository; +import hello.cluebackend.domain.directory.domain.Directory; +import hello.cluebackend.domain.directory.presentation.dto.DirectoryAllInfoDto; +import hello.cluebackend.domain.directory.presentation.dto.DirectoryDto; +import hello.cluebackend.domain.document.domain.Document; +import hello.cluebackend.domain.document.presentation.dto.DocumentAllInfoDto; +import hello.cluebackend.domain.document.presentation.dto.DocumentDto; import hello.cluebackend.domain.user.domain.Role; import hello.cluebackend.domain.user.domain.UserEntity; import hello.cluebackend.domain.user.domain.repository.UserRepository; +import jakarta.persistence.EntityNotFoundException; import jakarta.transaction.Transactional; import org.springframework.stereotype.Service; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; @Service public class ClassRoomService { @@ -26,10 +36,10 @@ public ClassRoomService(ClassRoomUserRepository classRoomUserRepository, ClassRo this.userRepository = userRepository; } - public List findMyClassRoomById(Long id) { - List classRoomUsers = classRoomUserRepository.findByUser_UserId(id); + public List findMyClassRoomById(Long userId) { + List classRoomUsers = classRoomUserRepository.findByUser_UserId(userId); return classRoomUsers.stream() - .filter(cu -> cu.getUser().getRole() == Role.STUDENT) +// .filter(cu -> cu.getUser().getRole() == Role.STUDENT) .map(ClassRoomUser::getClassRoom) .map(ClassRoom::toCardDTO) .toList(); @@ -41,7 +51,7 @@ public void createClassRoom(ClassRoomDto classRoomDTO, Long userId) { ClassRoom classRoom = classRoomDTO.toEntity(); classRoomRepository.save(classRoom); - UserEntity user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("user not found")); + UserEntity user = userRepository.findById(userId).orElseThrow(() -> new EntityNotFoundException("user not found")); ClassRoomUser classRoomUser = ClassRoomUser.builder() .classRoom(classRoom) @@ -74,4 +84,43 @@ public void updateClassRoom(Long classId, ClassRoomDto classRoomDTO) { findClassRoom.setIsActivation(classRoomDTO.getIsActivation()); classRoomRepository.save(findClassRoom); } + + public ClassRoomAllInfoDto getAllInfo(Long classId) { + ClassRoom classRoom = classRoomRepository.findById(classId) + .orElseThrow(() -> new IllegalArgumentException("해당 수업이 존재하지 않습니다.")); + + List directoryDtoList = classRoom.getDirectoryList().stream() + .sorted(Comparator.comparingInt(Directory::getDirectoryOrder)) // directoryOrder 기준 정렬 + .map(directory -> { + List documentDtoList = directory.getDocumentList().stream() + .sorted(Comparator.comparing(Document::getCreatedAt)) // createdAt 기준 정렬 + .map(doc -> DocumentAllInfoDto.builder() + .documentId(doc.getDocumentId()) + .title(doc.getTitle()) + .createdAt(doc.getCreatedAt()) + .build()) + .collect(Collectors.toList()); + + return DirectoryAllInfoDto.builder() + .directoryId(directory.getDirectoryId()) + .directoryName(directory.getName()) + .directoryOrder(directory.getDirectoryOrder()) + .documentList(documentDtoList) + .build(); + }).collect(Collectors.toList()); + List findUsers = classRoomUserRepository.findUsersByClassRoomId(classId); + + List teacherNames = findUsers.stream() + .filter(user -> user.getRole() == Role.TEACHER) + .map(UserEntity::getUsername) + .collect(Collectors.toList()); + + return ClassRoomAllInfoDto.builder() + .classRoomId(classRoom.getClassRoomId()) + .classRoomName(classRoom.getName()) + .description(classRoom.getDescription()) + .directoryList(directoryDtoList) + .teacherNames(teacherNames) + .build(); + } } diff --git a/src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java b/src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java index 5b861b8f..124c3f23 100644 --- a/src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java +++ b/src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java @@ -2,6 +2,7 @@ import hello.cluebackend.domain.classroom.domain.ClassRoom; import hello.cluebackend.domain.classroomuser.domain.ClassRoomUser; +import hello.cluebackend.domain.user.domain.UserEntity; import io.lettuce.core.dynamic.annotation.Param; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -16,4 +17,12 @@ public interface ClassRoomUserRepository extends JpaRepository findClassRoomsByUserId(@Param("userId") Long userId); + + @Query("select cu.user from ClassRoomUser cu where cu.classRoom.classRoomId = :classId") + List findUsersByClassRoomId(@Param("classId") Long classId); + + @Query("SELECT cru.user FROM ClassRoomUser cru WHERE cru.classRoom.classRoomId = :classRoomId") + List findAllStudentsByClassRoomId(Long classRoomId); + + List user(UserEntity user); } \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/directory/domain/Directory.java b/src/main/java/hello/cluebackend/domain/directory/domain/Directory.java index c769e861..ecc01a77 100644 --- a/src/main/java/hello/cluebackend/domain/directory/domain/Directory.java +++ b/src/main/java/hello/cluebackend/domain/directory/domain/Directory.java @@ -2,9 +2,12 @@ import hello.cluebackend.domain.classroom.domain.ClassRoom; import hello.cluebackend.domain.directory.presentation.dto.DirectoryDto; +import hello.cluebackend.domain.document.domain.Document; import jakarta.persistence.*; import lombok.*; +import java.util.List; + @Entity @Builder @NoArgsConstructor @@ -27,6 +30,9 @@ public class Directory { @Column(nullable = false) private int directoryOrder; + @OneToMany(mappedBy = "directory", cascade = CascadeType.ALL) + private List documentList; + public DirectoryDto toDto() { return DirectoryDto.builder() .directoryId(directoryId) diff --git a/src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java b/src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java new file mode 100644 index 00000000..f86b87c7 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/directory/presentation/dto/DirectoryAllInfoDto.java @@ -0,0 +1,18 @@ +package hello.cluebackend.domain.directory.presentation.dto; + +import hello.cluebackend.domain.document.presentation.dto.DocumentAllInfoDto; +import lombok.*; + +import java.util.List; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DirectoryAllInfoDto { + private Long directoryId; + private String directoryName; + private int directoryOrder; + private List documentList; +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/document/domain/Document.java b/src/main/java/hello/cluebackend/domain/document/domain/Document.java index 3a3a621e..49083402 100644 --- a/src/main/java/hello/cluebackend/domain/document/domain/Document.java +++ b/src/main/java/hello/cluebackend/domain/document/domain/Document.java @@ -4,10 +4,7 @@ import hello.cluebackend.domain.directory.domain.Directory; import hello.cluebackend.domain.document.presentation.dto.DocumentDto; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.time.LocalDateTime; @@ -16,6 +13,7 @@ @AllArgsConstructor @NoArgsConstructor @Getter +@Setter public class Document { @Id diff --git a/src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java b/src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java index 596360b3..6da14d15 100644 --- a/src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java +++ b/src/main/java/hello/cluebackend/domain/document/presentation/DocumentController.java @@ -1,6 +1,5 @@ package hello.cluebackend.domain.document.presentation; -import com.nimbusds.jose.util.Resource; import hello.cluebackend.domain.document.presentation.dto.DocumentDto; import hello.cluebackend.domain.document.presentation.dto.FileUpload; import hello.cluebackend.domain.document.presentation.dto.RequestDocumentDto; @@ -63,6 +62,30 @@ public ResponseEntity uploadDocument( return ResponseEntity.ok().build(); } + @PatchMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + public ResponseEntity updateDocument( + @RequestPart("metadata") List requestDocumentDto, + @RequestPart("files") List files, + @RequestPart("classRoomId") Long classRoomId, + @RequestPart("directoryId") Long directoryId, + HttpServletRequest request) { + + String token = jwtUtil.getToken(request); + Role role = jwtUtil.getRole(token); + + if(role != Role.TEACHER) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + try { + documentService.updateDocument(classRoomId, directoryId, requestDocumentDto, files); + return ResponseEntity.ok().build(); + } catch (IllegalArgumentException e) { + log.error(e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + } + @DeleteMapping public ResponseEntity deleteDocument(@RequestBody RequestDocumentDto requestDocumentDto, HttpServletRequest request) { String token = jwtUtil.getToken(request); @@ -81,24 +104,28 @@ public ResponseEntity deleteDocument(@RequestBody RequestDocumentDto requestD return ResponseEntity.ok().build(); } - @GetMapping("/download") - public ResponseEntity downloadDocument(@RequestParam("documentId") Long documentId) { + @GetMapping("/download/{documentId}") + public ResponseEntity downloadDocument(@PathVariable("documentId") Long documentId) { try { DocumentDto documentDto = documentService.findById(documentId); String fullPath = documentDto.getContent(); UrlResource resource = new UrlResource("file:" + fullPath); - } catch (IllegalArgumentException e) { - log.error(e.getMessage()); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); - } catch (MalformedURLException e) { + + String encodedFileName = UriUtils.encode(fullPath.split("_")[1], StandardCharsets.UTF_8); + String contentDisposition = "attachment; filename=\"" + encodedFileName + "\""; + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) + .body(resource); + } catch (MalformedURLException | IllegalArgumentException e) { log.error(e.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } - - return ResponseEntity.ok().build(); } + +// 테스트 용도 @PostMapping("/test") public ResponseEntity> uploadMultipartFileTest(@RequestParam("files") MultipartFile[] files, HttpServletRequest request) { String token = jwtUtil.getToken(request); @@ -112,17 +139,4 @@ public ResponseEntity> uploadMultipartFileTest(@RequestParam("f return ResponseEntity.ok(fileUploads); } - @GetMapping("/download/{filename}") - public ResponseEntity downloadFile(@PathVariable String filename) throws MalformedURLException { - String fullPath = localStorageService.getFullPath(filename); - UrlResource resource = new UrlResource("file:" + fullPath); - - String encodedFileName = UriUtils.encode(filename, StandardCharsets.UTF_8); - String contentDisposition = "attachment; filename=\"" + encodedFileName + "\""; - - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) - .body(resource); - } - } diff --git a/src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java b/src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java new file mode 100644 index 00000000..3af2fcb2 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/document/presentation/dto/DocumentAllInfoDto.java @@ -0,0 +1,16 @@ +package hello.cluebackend.domain.document.presentation.dto; + +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DocumentAllInfoDto { + private Long documentId; + private String title; + private LocalDateTime createdAt; +} diff --git a/src/main/java/hello/cluebackend/domain/document/service/DocumentService.java b/src/main/java/hello/cluebackend/domain/document/service/DocumentService.java index 5eeb7a4e..f1676f0e 100644 --- a/src/main/java/hello/cluebackend/domain/document/service/DocumentService.java +++ b/src/main/java/hello/cluebackend/domain/document/service/DocumentService.java @@ -16,6 +16,7 @@ import java.io.File; import java.io.IOException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -127,4 +128,40 @@ public DocumentDto findById(Long documentId) { Document findDocument = documentRepository.findById(documentId).orElseThrow(() -> new IllegalArgumentException("해당 수업자료가 존재하지 않습니다.")); return findDocument.toDto(); } + + public void updateDocument(Long classRoomId, Long directoryId, List requestDocumentDto, List files) { + System.out.println("directoryId = " + directoryId); + ClassRoom findClassRoom = classRoomRepository.findById(classRoomId).orElseThrow(() -> new IllegalArgumentException("해당 교실을 찾을 수가 없습니다.")); + Directory findDirectory = directoryRepository.findById(directoryId).orElseThrow(() -> new IllegalArgumentException("해당 디렉토를을 찾을 수가 없습니다.")); + System.out.println("통과"); + if(requestDocumentDto.size() != files.size()) { + throw new RuntimeException("한쪽 요소 부족"); + } + + for(int i = 0; i < requestDocumentDto.size(); i++) { + Document findDocument = documentRepository.findById(requestDocumentDto.get(i).getDocumentId()).orElseThrow(() -> new IllegalArgumentException("해당 수업을 찾지 못했습니다.")); + String fullPath = findDocument.getContent(); + + File file = new File(fullPath); + if (file.exists()) { + boolean deleted = file.delete(); + if (!deleted) { + log.warn("파일 삭제 실패: {}", fullPath); + } + } else { + log.warn("파일이 존재하지 않음: {}", fullPath); + } + + try { + FileUpload uploadResult = upload(files.get(i)); + findDocument.setTitle(requestDocumentDto.get(i).getTitle()); + findDocument.setType(requestDocumentDto.get(i).getType()); + findDocument.setContent(uploadResult.getFullPath()); + documentRepository.save(findDocument); + } catch(Exception e) { + log.error("Failed to store file {}: {}", files.get(i).getOriginalFilename(), e.getMessage()); + } + } + + } } diff --git a/src/main/java/hello/cluebackend/domain/timetable/service/TimetableService.java b/src/main/java/hello/cluebackend/domain/timetable/service/TimetableService.java index 6642081b..625eff96 100644 --- a/src/main/java/hello/cluebackend/domain/timetable/service/TimetableService.java +++ b/src/main/java/hello/cluebackend/domain/timetable/service/TimetableService.java @@ -75,7 +75,7 @@ public Mono> getWeeklyTimetable(TimetableRequestDto r return fetchTimetable(request, from, to); } - private Mono> fetchTimetable(TimetableRequestDto request, String from, String to) { + private Mono> fetchTimetable(TimetableRequestDto request, String from, String to) { LocalDate now = LocalDate.now(KOREA_TIMEZONE); String year = now.format(YEAR_FORMATTER); String semester = determineSemester(now); diff --git a/src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java b/src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java index 0e6751e8..d720defc 100644 --- a/src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java +++ b/src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.List; +@ToString @Entity @Getter @Setter @@ -47,6 +48,7 @@ protected void onCreate() { } @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + @Builder.Default private List classRoomUserList = new ArrayList<>(); public UserEntity(int classCode, String username, String email, Role role) { @@ -57,7 +59,6 @@ public UserEntity(int classCode, String username, String email, Role role) { } public UserDto toUserDTO() { -// return new UserDto(userId, email, role, username, classCode); return UserDto.builder() .userId(userId) .classCode(classCode) diff --git a/src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java b/src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java index 3067daca..77d768d8 100644 --- a/src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java +++ b/src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java @@ -39,7 +39,7 @@ public UserDto showRegistrationForm(HttpServletRequest request) { consumes = MediaType.APPLICATION_JSON_VALUE ) public ResponseEntity processRegistration(@RequestBody DefaultRegisterUserDto defaultRegisterUserDTO) { - log.debug("ClassCode 1 : " + defaultRegisterUserDTO.getClassCode()); + log.info("ClassCode 1 : " + defaultRegisterUserDTO.getClassCode()); userService.registerUser(defaultRegisterUserDTO); return new ResponseEntity<>(HttpStatus.CREATED); } diff --git a/src/main/java/hello/cluebackend/domain/user/presentation/TestController.java b/src/main/java/hello/cluebackend/domain/user/presentation/TestController.java index 311e982f..86f68bba 100644 --- a/src/main/java/hello/cluebackend/domain/user/presentation/TestController.java +++ b/src/main/java/hello/cluebackend/domain/user/presentation/TestController.java @@ -18,7 +18,7 @@ public class TestController { @PostMapping("/test") public ResponseEntity issueToken(@RequestParam Long userId, @RequestParam String username, @RequestParam String role, HttpServletResponse response) { - String access = jwtUtil.createJwt("access", userId, username, role, 60 * 60 * 1000L); + String access = jwtUtil.createJwt("access", userId, username, role, 100 * 60 * 60 * 1000L); return ResponseEntity.ok() .header("Authorization", "Bearer " + access) diff --git a/src/main/java/hello/cluebackend/global/config/CorsMvcConfig.java b/src/main/java/hello/cluebackend/global/config/CorsMvcConfig.java index 3be43a1a..bd20a540 100644 --- a/src/main/java/hello/cluebackend/global/config/CorsMvcConfig.java +++ b/src/main/java/hello/cluebackend/global/config/CorsMvcConfig.java @@ -11,7 +11,8 @@ public class CorsMvcConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry corsRegistry) { corsRegistry.addMapping("/**") .exposedHeaders("Set-Cookie") - .allowedOrigins("http://localhost:3000") + .allowedOrigins("http://10.129.57.136:3000") +// .allowedOrigins("https://clue-frontend-eight.vercel.app") .allowedHeaders("*") .exposedHeaders("Authorization") .allowCredentials(true); diff --git a/src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java b/src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java index f2452e51..2eaae1c4 100644 --- a/src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java +++ b/src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java @@ -37,10 +37,15 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo int classCode = userDTO.getClassCode(); if (classCode == -1) { request.getSession().setAttribute("firstUser", userDTO); +// getRedirectStrategy().sendRedirect( +// request, +// response, +// "http://localhost:3000/register" +// ); getRedirectStrategy().sendRedirect( request, response, - "http://localhost:3000/register" + "https://clue-frontend-eight.vercel.app/register" ); } else { String username = customUserDetails.getUsername(); @@ -58,7 +63,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setHeader("Authorization", "Bearer " + access); response.addCookie(createCookie("refresh_token", refresh)); response.setStatus(HttpStatus.OK.value()); - response.sendRedirect("http://localhost:3000/"); +// response.sendRedirect("http://localhost:3000/"); + response.sendRedirect("https://clue-frontend-eight.vercel.app"); } } diff --git a/src/main/java/hello/cluebackend/global/config/FileUtil.java b/src/main/java/hello/cluebackend/global/config/FileUtil.java new file mode 100644 index 00000000..88631172 --- /dev/null +++ b/src/main/java/hello/cluebackend/global/config/FileUtil.java @@ -0,0 +1,16 @@ +package hello.cluebackend.global.config; + +import java.util.UUID; + +public class FileUtil { + public static String generateUniqueFileName(String originalFileName) { + if (originalFileName == null || !originalFileName.contains(".")) { + throw new IllegalArgumentException("파일 이름이 유효하지 않습니다."); + } + + String ext = originalFileName.substring(originalFileName.lastIndexOf(".")); + String uuid = UUID.randomUUID().toString(); + + return uuid + ext; + } +} diff --git a/src/main/java/hello/cluebackend/global/config/S3Config.java b/src/main/java/hello/cluebackend/global/config/S3Config.java new file mode 100644 index 00000000..1dd0c16f --- /dev/null +++ b/src/main/java/hello/cluebackend/global/config/S3Config.java @@ -0,0 +1,30 @@ +package hello.cluebackend.global.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) + .build(); + } +} diff --git a/src/main/java/hello/cluebackend/global/config/S3Uploader.java b/src/main/java/hello/cluebackend/global/config/S3Uploader.java new file mode 100644 index 00000000..94eb2042 --- /dev/null +++ b/src/main/java/hello/cluebackend/global/config/S3Uploader.java @@ -0,0 +1,48 @@ +package hello.cluebackend.global.config; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.ObjectMetadata; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class S3Uploader { + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + private final AmazonS3Client amazonS3Client; + + public String upload(MultipartFile file, String dirName) { + String originalName = file.getOriginalFilename(); + String fileName = createFileName(originalName); + String filePath = dirName + "/" + fileName; + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + metadata.setContentType(file.getContentType()); + + try (InputStream inputStream = file.getInputStream()) { + amazonS3Client.putObject(bucket, filePath, inputStream, metadata); + } catch (IOException e) { + throw new RuntimeException("파일 업로드 실패", e); + } + + return amazonS3Client.getUrl(bucket, filePath).toString(); // 업로드된 파일 URL 반환 + } + + private String createFileName(String originalName) { + String ext = originalName.substring(originalName.lastIndexOf(".")); + return UUID.randomUUID().toString() + ext; + } + + public void delete(String filePath) { + amazonS3Client.deleteObject(bucket, filePath); + } +} diff --git a/src/main/java/hello/cluebackend/global/config/SecurityConfig.java b/src/main/java/hello/cluebackend/global/config/SecurityConfig.java index f2839f9f..8a14cc46 100644 --- a/src/main/java/hello/cluebackend/global/config/SecurityConfig.java +++ b/src/main/java/hello/cluebackend/global/config/SecurityConfig.java @@ -114,7 +114,8 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { // 경로별 인가 작업 http .authorizeHttpRequests(auth -> auth - .requestMatchers("/", "/refresh-token", "/register", "/first-register", "/api/timetable/**", "/test", "/api/document/**").permitAll() +// .requestMatchers("/", "/refresh-token", "/register", "/first-register", "/api/timetable/**", "/test", "/api/document/download/{documentId:\\d+}").permitAll() + .requestMatchers("/", "/refresh-token", "/register", "/first-register", "/api/timetable/**", "/test").permitAll() .anyRequest().authenticated()); diff --git a/src/main/resources/.DS_Store b/src/main/resources/.DS_Store index 498cc424..6fbe9dc2 100644 Binary files a/src/main/resources/.DS_Store and b/src/main/resources/.DS_Store differ diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index c510bef3..af892e29 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -2,10 +2,10 @@ spring: application: name: CLUE-Backend datasource: -# url: jdbc:postgresql://my_postgres:5432/clue - url: jdbc:postgresql://localhost:5432/clue - username: clue1234 - password: clue52025 +# url: jdbc:postgresql://localhost:5432/clue + url: ${POSTGRES_URL} + username: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} driver-class-name: org.postgresql.Driver jpa: database-platform: org.hibernate.dialect.PostgreSQLDialect @@ -50,6 +50,18 @@ upload: local: dir: ${UPLOAD_FILE_DIR} +cloud: + aws: + credentials: + accessKey: ${AWS_ACCESS_KEY} + secretKey: ${AWS_SECRET_KEY} + s3: + bucket : ${AWS_BUCKET_NAME} + region: + static: ap-northeast-2 + stack: + auto: false + server: address: 0.0.0.0 port: 8080 \ No newline at end of file