Skip to content

Commit 89856b2

Browse files
authored
v2.3.5 (#769)
2 parents 00cc765 + e9e4c62 commit 89856b2

20 files changed

+107
-111
lines changed

src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyController.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.gdschongik.gdsc.domain.study.application.MentorStudyService;
44
import com.gdschongik.gdsc.domain.study.dto.request.StudyAnnouncementCreateUpdateRequest;
55
import com.gdschongik.gdsc.domain.study.dto.request.StudyUpdateRequest;
6-
import com.gdschongik.gdsc.domain.study.dto.response.MentorStudyResponse;
6+
import com.gdschongik.gdsc.domain.study.dto.response.StudyResponse;
77
import com.gdschongik.gdsc.domain.study.dto.response.StudyStudentResponse;
88
import io.swagger.v3.oas.annotations.Operation;
99
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -30,8 +30,8 @@ public ResponseEntity<Void> updateStudy(@PathVariable Long studyId, @RequestBody
3030

3131
@Operation(summary = "내 스터디 조회", description = "내가 멘토로 있는 스터디를 조회합니다.")
3232
@GetMapping("/me")
33-
public ResponseEntity<List<MentorStudyResponse>> getStudiesInCharge() {
34-
List<MentorStudyResponse> response = mentorStudyService.getStudiesInCharge();
33+
public ResponseEntity<List<StudyResponse>> getStudiesInCharge() {
34+
List<StudyResponse> response = mentorStudyService.getStudiesInCharge();
3535
return ResponseEntity.ok(response);
3636
}
3737

src/main/java/com/gdschongik/gdsc/domain/study/api/StudentStudyDetailController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public ResponseEntity<List<StudyStudentCurriculumResponse>> getStudyCurriculums(
4646
return ResponseEntity.ok(response);
4747
}
4848

49+
@Deprecated
4950
@Operation(summary = "이번주 제출해야 할 과제 조회", description = "마감 기한이 이번주까지인 과제를 조회합니다.")
5051
@GetMapping("/assignments/upcoming")
5152
public ResponseEntity<List<AssignmentHistoryStatusResponse>> getUpcomingAssignments(
Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
package com.gdschongik.gdsc.domain.study.application;
22

3-
import static com.gdschongik.gdsc.global.exception.ErrorCode.STUDY_NOT_FOUND;
3+
import static com.gdschongik.gdsc.global.exception.ErrorCode.*;
44

5-
import com.gdschongik.gdsc.domain.member.domain.Member;
5+
import com.gdschongik.gdsc.domain.study.dao.AssignmentHistoryRepository;
6+
import com.gdschongik.gdsc.domain.study.dao.AttendanceRepository;
67
import com.gdschongik.gdsc.domain.study.dao.StudyAnnouncementRepository;
78
import com.gdschongik.gdsc.domain.study.dao.StudyHistoryRepository;
89
import com.gdschongik.gdsc.domain.study.dao.StudyRepository;
910
import com.gdschongik.gdsc.domain.study.domain.Study;
1011
import com.gdschongik.gdsc.domain.study.domain.StudyAnnouncement;
11-
import com.gdschongik.gdsc.domain.study.domain.StudyHistory;
1212
import com.gdschongik.gdsc.domain.study.domain.StudyValidator;
1313
import com.gdschongik.gdsc.domain.study.dto.response.CommonStudyResponse;
1414
import com.gdschongik.gdsc.domain.study.dto.response.StudyAnnouncementResponse;
1515
import com.gdschongik.gdsc.global.exception.CustomException;
1616
import com.gdschongik.gdsc.global.util.MemberUtil;
1717
import java.util.List;
18-
import java.util.Optional;
1918
import lombok.RequiredArgsConstructor;
2019
import lombok.extern.slf4j.Slf4j;
2120
import org.springframework.stereotype.Service;
@@ -29,6 +28,8 @@ public class CommonStudyService {
2928
private final StudyRepository studyRepository;
3029
private final StudyHistoryRepository studyHistoryRepository;
3130
private final StudyAnnouncementRepository studyAnnouncementRepository;
31+
private final AttendanceRepository attendanceRepository;
32+
private final AssignmentHistoryRepository assignmentHistoryRepository;
3233
private final MemberUtil memberUtil;
3334
private final StudyValidator studyValidator;
3435

@@ -40,15 +41,23 @@ public CommonStudyResponse getStudyInformation(Long studyId) {
4041

4142
@Transactional(readOnly = true)
4243
public List<StudyAnnouncementResponse> getStudyAnnouncements(Long studyId) {
43-
Member currentMember = memberUtil.getCurrentMember();
44-
final Study study = studyRepository.findById(studyId).orElseThrow(() -> new CustomException(STUDY_NOT_FOUND));
45-
Optional<StudyHistory> studyHistory = studyHistoryRepository.findByStudentAndStudyId(currentMember, studyId);
46-
47-
studyValidator.validateStudyMentorOrStudent(currentMember, study, studyHistory);
48-
4944
final List<StudyAnnouncement> studyAnnouncements =
5045
studyAnnouncementRepository.findAllByStudyIdOrderByCreatedAtDesc(studyId);
5146

5247
return studyAnnouncements.stream().map(StudyAnnouncementResponse::from).toList();
5348
}
49+
50+
/**
51+
* 이벤트 핸들러에서 사용되므로, `@Transactional` 을 사용하지 않습니다.
52+
*/
53+
public void deleteAttendance(Long studyId, Long memberId) {
54+
attendanceRepository.deleteByStudyIdAndMemberId(studyId, memberId);
55+
}
56+
57+
/**
58+
* 이벤트 핸들러에서 사용되므로, `@Transactional` 을 사용하지 않습니다.
59+
*/
60+
public void deleteAssignmentHistory(Long studyId, Long memberId) {
61+
assignmentHistoryRepository.deleteByStudyIdAndMemberId(studyId, memberId);
62+
}
5463
}

src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import com.gdschongik.gdsc.domain.study.dto.request.StudyAnnouncementCreateUpdateRequest;
1616
import com.gdschongik.gdsc.domain.study.dto.request.StudyCurriculumCreateRequest;
1717
import com.gdschongik.gdsc.domain.study.dto.request.StudyUpdateRequest;
18-
import com.gdschongik.gdsc.domain.study.dto.response.MentorStudyResponse;
18+
import com.gdschongik.gdsc.domain.study.dto.response.StudyResponse;
1919
import com.gdschongik.gdsc.domain.study.dto.response.StudyStudentResponse;
2020
import com.gdschongik.gdsc.global.exception.CustomException;
2121
import com.gdschongik.gdsc.global.exception.ErrorCode;
@@ -42,10 +42,10 @@ public class MentorStudyService {
4242
private final StudyDetailValidator studyDetailValidator;
4343

4444
@Transactional(readOnly = true)
45-
public List<MentorStudyResponse> getStudiesInCharge() {
45+
public List<StudyResponse> getStudiesInCharge() {
4646
Member currentMember = memberUtil.getCurrentMember();
4747
List<Study> myStudies = studyRepository.findAllByMentor(currentMember);
48-
return myStudies.stream().map(MentorStudyResponse::from).toList();
48+
return myStudies.stream().map(StudyResponse::from).toList();
4949
}
5050

5151
@Transactional(readOnly = true)

src/main/java/com/gdschongik/gdsc/domain/study/application/StudentStudyService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public void applyStudy(Long studyId) {
7070

7171
@Transactional
7272
public void cancelStudyApply(Long studyId) {
73+
// TODO: 통합 테스트 통해 수강철회 관련 이벤트 처리 확인
7374
Study study = studyRepository.findById(studyId).orElseThrow(() -> new CustomException(STUDY_NOT_FOUND));
7475
Member currentMember = memberUtil.getCurrentMember();
7576

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.gdschongik.gdsc.domain.study.application;
2+
3+
import com.gdschongik.gdsc.domain.study.domain.StudyApplyCanceledEvent;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.stereotype.Component;
7+
import org.springframework.transaction.event.TransactionPhase;
8+
import org.springframework.transaction.event.TransactionalEventListener;
9+
10+
@Slf4j
11+
@Component
12+
@RequiredArgsConstructor
13+
public class StudyEventHandler {
14+
15+
private final CommonStudyService commonStudyService;
16+
17+
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
18+
public void handleStudyApplyCanceledEvent(StudyApplyCanceledEvent event) {
19+
log.info("[StudyEventHandler] 스터디 수강신청 취소 이벤트 수신: studyId={}, memberId={}", event.studyId(), event.memberId());
20+
commonStudyService.deleteAttendance(event.studyId(), event.memberId());
21+
commonStudyService.deleteAssignmentHistory(event.studyId(), event.memberId());
22+
}
23+
}

src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryCustomRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ public interface AssignmentHistoryCustomRepository {
1010
boolean existsSubmittedAssignmentByMemberAndStudy(Member member, Study study);
1111

1212
List<AssignmentHistory> findAssignmentHistoriesByStudentAndStudyId(Member member, Long studyId);
13+
14+
void deleteByStudyIdAndMemberId(Long studyId, Long memberId);
1315
}

src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryCustomRepositoryImpl.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,16 @@ public List<AssignmentHistory> findAssignmentHistoriesByStudentAndStudyId(Member
5353
private BooleanExpression eqStudyId(Long studyId) {
5454
return studyId != null ? studyDetail.study.id.eq(studyId) : null;
5555
}
56+
57+
@Override
58+
public void deleteByStudyIdAndMemberId(Long studyId, Long memberId) {
59+
queryFactory
60+
.delete(assignmentHistory)
61+
.where(eqMemberId(memberId), eqStudyId(studyId))
62+
.execute();
63+
}
64+
65+
private BooleanExpression eqMemberId(Long memberId) {
66+
return memberId != null ? assignmentHistory.member.id.eq(memberId) : null;
67+
}
5668
}

src/main/java/com/gdschongik/gdsc/domain/study/dao/AttendanceCustomRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66

77
public interface AttendanceCustomRepository {
88
List<Attendance> findByMemberAndStudyId(Member member, Long studyId);
9+
10+
void deleteByStudyIdAndMemberId(Long studyId, Long memberId);
911
}

src/main/java/com/gdschongik/gdsc/domain/study/dao/AttendanceCustomRepositoryImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,12 @@ private BooleanExpression eqMemberId(Long memberId) {
3333
private BooleanExpression eqStudyId(Long studyId) {
3434
return studyId != null ? attendance.studyDetail.study.id.eq(studyId) : null;
3535
}
36+
37+
@Override
38+
public void deleteByStudyIdAndMemberId(Long studyId, Long memberId) {
39+
queryFactory
40+
.delete(attendance)
41+
.where(eqMemberId(memberId), eqStudyId(studyId))
42+
.execute();
43+
}
3644
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.gdschongik.gdsc.domain.study.domain;
2+
3+
public record StudyApplyCanceledEvent(Long studyId, Long memberId) {}

src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyDetailValidator.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public void validateCancelStudyAssignment(Member member, StudyDetail studyDetail
1919
public void validatePublishStudyAssignment(
2020
Member member, StudyDetail studyDetail, AssignmentCreateUpdateRequest request) {
2121
validateStudyMentorAuthorization(member, studyDetail);
22-
validateDeadLine(request.deadLine());
22+
validateDeadLine(request.deadLine(), studyDetail.getPeriod().getStartDate());
2323
}
2424

2525
// 해당 스터디의 멘토가 아니라면 스터디에 대한 권한이 없다.
@@ -29,8 +29,8 @@ private void validateStudyMentorAuthorization(Member member, StudyDetail studyDe
2929
}
3030
}
3131

32-
private void validateDeadLine(LocalDateTime deadline) {
33-
if (deadline.isBefore(LocalDateTime.now())) {
32+
private void validateDeadLine(LocalDateTime deadline, LocalDateTime studyStartDate) {
33+
if (deadline.isBefore(LocalDateTime.now()) || deadline.isBefore(studyStartDate)) {
3434
throw new CustomException(ASSIGNMENT_DEADLINE_INVALID);
3535
}
3636
}

src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistory.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import jakarta.persistence.Id;
1111
import jakarta.persistence.JoinColumn;
1212
import jakarta.persistence.ManyToOne;
13+
import jakarta.persistence.PreRemove;
1314
import jakarta.persistence.Table;
1415
import jakarta.persistence.UniqueConstraint;
1516
import lombok.AccessLevel;
@@ -48,6 +49,11 @@ public static StudyHistory create(Member student, Study study) {
4849
return StudyHistory.builder().student(student).study(study).build();
4950
}
5051

52+
@PreRemove
53+
private void preRemove() {
54+
registerEvent(new StudyApplyCanceledEvent(this.study.getId(), this.student.getId()));
55+
}
56+
5157
/**
5258
* 레포지토리 링크를 업데이트합니다.
5359
*/

src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyValidator.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import com.gdschongik.gdsc.domain.member.domain.Member;
66
import com.gdschongik.gdsc.global.annotation.DomainService;
77
import com.gdschongik.gdsc.global.exception.CustomException;
8-
import java.util.Optional;
98

109
@DomainService
1110
public class StudyValidator {
@@ -25,21 +24,4 @@ public void validateStudyMentor(Member currentMember, Study study) {
2524
throw new CustomException(STUDY_MENTOR_INVALID);
2625
}
2726
}
28-
29-
public void validateStudyMentorOrStudent(Member currentMember, Study study, Optional<StudyHistory> studyHistory) {
30-
// 어드민인 경우 검증 통과
31-
if (currentMember.isAdmin()) {
32-
return;
33-
}
34-
35-
// 해당 스터디의 수강생인지 검증
36-
if (currentMember.isStudent() && studyHistory.isEmpty()) {
37-
throw new CustomException(STUDY_ACCESS_NOT_ALLOWED);
38-
}
39-
40-
// 해당 스터디의 담당 멘토인지 검증
41-
if (!currentMember.getId().equals(study.getMentor().getId())) {
42-
throw new CustomException(STUDY_MENTOR_INVALID);
43-
}
44-
}
4527
}

src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentResponse.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ public record AssignmentResponse(
1313
@Schema(description = "마감 기한") LocalDateTime deadline,
1414
@Schema(description = "주차") Long week,
1515
@Schema(description = "과제 명세 링크") String descriptionLink,
16-
@Schema(description = "과제 상태") StudyStatus assignmentStatus) {
16+
@Schema(description = "과제 상태") StudyStatus assignmentStatus,
17+
@Schema(description = "주차 시작일") LocalDateTime studyDetailStartDate) {
1718
public static AssignmentResponse from(StudyDetail studyDetail) {
1819
Assignment assignment = studyDetail.getAssignment();
1920
return new AssignmentResponse(
@@ -23,6 +24,7 @@ public static AssignmentResponse from(StudyDetail studyDetail) {
2324
assignment.getDeadline(),
2425
studyDetail.getWeek(),
2526
assignment.getDescriptionLink(),
26-
assignment.getStatus());
27+
assignment.getStatus(),
28+
studyDetail.getPeriod().getStartDate());
2729
}
2830
}

src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentSubmittableDto.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public record AssignmentSubmittableDto(
1919
@Nullable @Schema(description = "과제 명세 링크") String descriptionLink,
2020
@Nullable @Schema(description = "마감 기한") LocalDateTime deadline,
2121
@Nullable @Schema(description = "과제 제출 링크") String submissionLink,
22-
@Nullable @Schema(description = "과제 제출 실패 사유") SubmissionFailureType submissionFailureType) {
22+
@Nullable @Schema(description = "과제 제출 실패 사유") SubmissionFailureType submissionFailureType,
23+
@Nullable @Schema(description = "최종 수정 일시") LocalDateTime committedAt) {
2324
public static AssignmentSubmittableDto of(StudyDetail studyDetail, AssignmentHistory assignmentHistory) {
2425
Assignment assignment = studyDetail.getAssignment();
2526

@@ -40,12 +41,22 @@ public static AssignmentSubmittableDto of(StudyDetail studyDetail, AssignmentHis
4041
assignment.getDescriptionLink(),
4142
assignment.getDeadline(),
4243
assignmentHistory.getSubmissionLink(),
43-
assignmentHistory.getSubmissionFailureType());
44+
assignmentHistory.getSubmissionFailureType(),
45+
assignmentHistory.getCommittedAt());
4446
}
4547

4648
private static AssignmentSubmittableDto cancelledAssignment(StudyDetail studyDetail, Assignment assignment) {
4749
return new AssignmentSubmittableDto(
48-
studyDetail.getId(), assignment.getStatus(), studyDetail.getWeek(), null, null, null, null, null, null);
50+
studyDetail.getId(),
51+
assignment.getStatus(),
52+
studyDetail.getWeek(),
53+
null,
54+
null,
55+
null,
56+
null,
57+
null,
58+
null,
59+
null);
4960
}
5061

5162
private static AssignmentSubmittableDto beforeAssignmentSubmit(StudyDetail studyDetail, Assignment assignment) {
@@ -58,6 +69,7 @@ private static AssignmentSubmittableDto beforeAssignmentSubmit(StudyDetail study
5869
assignment.getDescriptionLink(),
5970
assignment.getDeadline(),
6071
null,
72+
null,
6173
null);
6274
}
6375
}

src/main/java/com/gdschongik/gdsc/domain/study/dto/response/MentorStudyResponse.java

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/main/java/com/gdschongik/gdsc/global/common/constant/GithubConstant.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
public class GithubConstant {
44

55
public static final String GITHUB_DOMAIN = "github.com/";
6-
public static final String GITHUB_ASSIGNMENT_PATH = "week%d/WIL.md";
6+
public static final String GITHUB_ASSIGNMENT_PATH = "week%d/wil.md";
77
public static final String GITHUB_USER_API_URL = "https://api.github.com/user/%s";
88

99
private GithubConstant() {}

0 commit comments

Comments
 (0)