From f825b62409a9dd3e223b17d086df62126278ad35 Mon Sep 17 00:00:00 2001 From: autocommit Date: Mon, 13 Apr 2026 23:41:56 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9D=B4=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EA=B3=BC=EC=A0=9C=20=EB=8F=99=EA=B8=B0=ED=99=94=20=EB=B0=B1?= =?UTF-8?q?=EC=97=94=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EclassAssignment 엔티티 추가 (member + eclassId 유니크 제약) - 과제 저장 시 이미 존재하면 update, 없으면 insert (upsert) - POST /eclass/assignments: 스크래핑된 과제 일괄 저장 - GET /eclass/assignments: 사용자별 저장된 과제 목록 조회 Co-Authored-By: Claude Sonnet 4.6 --- .../EclassAssignmentController.java | 41 ++++++++++ .../eclass/dto/EclassAssignmentItem.java | 29 +++++++ .../eclass/dto/EclassAssignmentResponse.java | 22 +++++ .../dto/EclassAssignmentSaveRequest.java | 16 ++++ .../eclass/entity/EclassAssignment.java | 69 ++++++++++++++++ .../EclassAssignmentRepository.java | 13 +++ .../service/EclassAssignmentService.java | 12 +++ .../service/EclassAssignmentServiceImpl.java | 80 +++++++++++++++++++ 8 files changed, 282 insertions(+) create mode 100644 src/main/java/com/dongsoop/dongsoop/eclass/controller/EclassAssignmentController.java create mode 100644 src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentItem.java create mode 100644 src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentResponse.java create mode 100644 src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentSaveRequest.java create mode 100644 src/main/java/com/dongsoop/dongsoop/eclass/entity/EclassAssignment.java create mode 100644 src/main/java/com/dongsoop/dongsoop/eclass/repository/EclassAssignmentRepository.java create mode 100644 src/main/java/com/dongsoop/dongsoop/eclass/service/EclassAssignmentService.java create mode 100644 src/main/java/com/dongsoop/dongsoop/eclass/service/EclassAssignmentServiceImpl.java diff --git a/src/main/java/com/dongsoop/dongsoop/eclass/controller/EclassAssignmentController.java b/src/main/java/com/dongsoop/dongsoop/eclass/controller/EclassAssignmentController.java new file mode 100644 index 00000000..e743b31d --- /dev/null +++ b/src/main/java/com/dongsoop/dongsoop/eclass/controller/EclassAssignmentController.java @@ -0,0 +1,41 @@ +package com.dongsoop.dongsoop.eclass.controller; + +import com.dongsoop.dongsoop.eclass.dto.EclassAssignmentResponse; +import com.dongsoop.dongsoop.eclass.dto.EclassAssignmentSaveRequest; +import com.dongsoop.dongsoop.eclass.service.EclassAssignmentService; +import com.dongsoop.dongsoop.member.service.MemberService; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/eclass/assignments") +@RequiredArgsConstructor +public class EclassAssignmentController { + + private final EclassAssignmentService eclassAssignmentService; + private final MemberService memberService; + + @PostMapping + public ResponseEntity> saveAssignments( + @Valid @RequestBody EclassAssignmentSaveRequest request) { + Long memberId = memberService.getMemberIdByAuthentication(); + List result = eclassAssignmentService.saveAssignments(memberId, request); + + return ResponseEntity.ok(result); + } + + @GetMapping + public ResponseEntity> getAssignments() { + Long memberId = memberService.getMemberIdByAuthentication(); + List result = eclassAssignmentService.getAssignments(memberId); + + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentItem.java b/src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentItem.java new file mode 100644 index 00000000..5c6c8406 --- /dev/null +++ b/src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentItem.java @@ -0,0 +1,29 @@ +package com.dongsoop.dongsoop.eclass.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class EclassAssignmentItem { + + @NotBlank(message = "이클래스 과제 ID를 입력해주세요.") + private String eclassId; + + private String courseId; + + @NotBlank(message = "수업명을 입력해주세요.") + private String courseName; + + @NotBlank(message = "과제명을 입력해주세요.") + private String title; + + private String dueDate; + + private boolean isSubmitted; + + private String status; + + private String link; +} \ No newline at end of file diff --git a/src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentResponse.java b/src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentResponse.java new file mode 100644 index 00000000..8de28900 --- /dev/null +++ b/src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentResponse.java @@ -0,0 +1,22 @@ +package com.dongsoop.dongsoop.eclass.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class EclassAssignmentResponse { + + private Long id; + private String eclassId; + private String courseId; + private String courseName; + private String title; + private String dueDate; + private boolean isSubmitted; + private String status; + private String link; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentSaveRequest.java b/src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentSaveRequest.java new file mode 100644 index 00000000..6079da94 --- /dev/null +++ b/src/main/java/com/dongsoop/dongsoop/eclass/dto/EclassAssignmentSaveRequest.java @@ -0,0 +1,16 @@ +package com.dongsoop.dongsoop.eclass.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class EclassAssignmentSaveRequest { + + @NotNull(message = "과제 목록이 필요합니다.") + @Valid + private List assignments; +} diff --git a/src/main/java/com/dongsoop/dongsoop/eclass/entity/EclassAssignment.java b/src/main/java/com/dongsoop/dongsoop/eclass/entity/EclassAssignment.java new file mode 100644 index 00000000..6cbe7a8c --- /dev/null +++ b/src/main/java/com/dongsoop/dongsoop/eclass/entity/EclassAssignment.java @@ -0,0 +1,69 @@ +package com.dongsoop.dongsoop.eclass.entity; + +import com.dongsoop.dongsoop.common.BaseEntity; +import com.dongsoop.dongsoop.member.entity.Member; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table( + name = "eclass_assignment", + uniqueConstraints = @UniqueConstraint(columnNames = {"member_id", "eclass_id"}) +) +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class EclassAssignment extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + @Column(name = "eclass_id", nullable = false, length = 50) + private String eclassId; + + @Column(name = "course_id", length = 50) + private String courseId; + + @Column(name = "course_name", nullable = false, length = 200) + private String courseName; + + @Column(name = "title", nullable = false, length = 500) + private String title; + + @Column(name = "due_date", length = 200) + private String dueDate; + + @Column(name = "is_submitted", nullable = false) + private boolean isSubmitted; + + @Column(name = "status", length = 100) + private String status; + + @Column(name = "link", length = 500) + private String link; + + public void update(String dueDate, boolean isSubmitted, String status) { + this.dueDate = dueDate; + this.isSubmitted = isSubmitted; + this.status = status; + } +} \ No newline at end of file diff --git a/src/main/java/com/dongsoop/dongsoop/eclass/repository/EclassAssignmentRepository.java b/src/main/java/com/dongsoop/dongsoop/eclass/repository/EclassAssignmentRepository.java new file mode 100644 index 00000000..bb1274ea --- /dev/null +++ b/src/main/java/com/dongsoop/dongsoop/eclass/repository/EclassAssignmentRepository.java @@ -0,0 +1,13 @@ +package com.dongsoop.dongsoop.eclass.repository; + +import com.dongsoop.dongsoop.eclass.entity.EclassAssignment; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EclassAssignmentRepository extends JpaRepository { + + List findByMemberIdAndIsDeletedFalseOrderByCreatedAtDesc(Long memberId); + + Optional findByMemberIdAndEclassIdAndIsDeletedFalse(Long memberId, String eclassId); +} diff --git a/src/main/java/com/dongsoop/dongsoop/eclass/service/EclassAssignmentService.java b/src/main/java/com/dongsoop/dongsoop/eclass/service/EclassAssignmentService.java new file mode 100644 index 00000000..a361fe2e --- /dev/null +++ b/src/main/java/com/dongsoop/dongsoop/eclass/service/EclassAssignmentService.java @@ -0,0 +1,12 @@ +package com.dongsoop.dongsoop.eclass.service; + +import com.dongsoop.dongsoop.eclass.dto.EclassAssignmentResponse; +import com.dongsoop.dongsoop.eclass.dto.EclassAssignmentSaveRequest; +import java.util.List; + +public interface EclassAssignmentService { + + List saveAssignments(Long memberId, EclassAssignmentSaveRequest request); + + List getAssignments(Long memberId); +} diff --git a/src/main/java/com/dongsoop/dongsoop/eclass/service/EclassAssignmentServiceImpl.java b/src/main/java/com/dongsoop/dongsoop/eclass/service/EclassAssignmentServiceImpl.java new file mode 100644 index 00000000..c53413ed --- /dev/null +++ b/src/main/java/com/dongsoop/dongsoop/eclass/service/EclassAssignmentServiceImpl.java @@ -0,0 +1,80 @@ +package com.dongsoop.dongsoop.eclass.service; + +import com.dongsoop.dongsoop.eclass.dto.EclassAssignmentItem; +import com.dongsoop.dongsoop.eclass.dto.EclassAssignmentResponse; +import com.dongsoop.dongsoop.eclass.dto.EclassAssignmentSaveRequest; +import com.dongsoop.dongsoop.eclass.entity.EclassAssignment; +import com.dongsoop.dongsoop.eclass.repository.EclassAssignmentRepository; +import com.dongsoop.dongsoop.member.entity.Member; +import com.dongsoop.dongsoop.member.exception.MemberNotFoundException; +import com.dongsoop.dongsoop.member.repository.MemberRepository; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class EclassAssignmentServiceImpl implements EclassAssignmentService { + + private final EclassAssignmentRepository eclassAssignmentRepository; + private final MemberRepository memberRepository; + + @Override + @Transactional + public List saveAssignments(Long memberId, EclassAssignmentSaveRequest request) { + Member member = memberRepository.findById(memberId) + .orElseThrow(MemberNotFoundException::new); + + for (EclassAssignmentItem item : request.getAssignments()) { + Optional existing = eclassAssignmentRepository + .findByMemberIdAndEclassIdAndIsDeletedFalse(memberId, item.getEclassId()); + + if (existing.isPresent()) { + existing.get().update(item.getDueDate(), item.isSubmitted(), item.getStatus()); + } else { + EclassAssignment assignment = EclassAssignment.builder() + .member(member) + .eclassId(item.getEclassId()) + .courseId(item.getCourseId()) + .courseName(item.getCourseName()) + .title(item.getTitle()) + .dueDate(item.getDueDate()) + .isSubmitted(item.isSubmitted()) + .status(item.getStatus()) + .link(item.getLink()) + .build(); + eclassAssignmentRepository.save(assignment); + } + } + + return getAssignments(memberId); + } + + @Override + @Transactional(readOnly = true) + public List getAssignments(Long memberId) { + return eclassAssignmentRepository + .findByMemberIdAndIsDeletedFalseOrderByCreatedAtDesc(memberId) + .stream() + .map(this::toResponse) + .toList(); + } + + private EclassAssignmentResponse toResponse(EclassAssignment assignment) { + return new EclassAssignmentResponse( + assignment.getId(), + assignment.getEclassId(), + assignment.getCourseId(), + assignment.getCourseName(), + assignment.getTitle(), + assignment.getDueDate(), + assignment.isSubmitted(), + assignment.getStatus(), + assignment.getLink(), + assignment.getCreatedAt(), + assignment.getUpdatedAt() + ); + } +}