-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 레포지토리 입력 API 추가 #594
Changes from 10 commits
a0eb5e6
c34d2dd
fc70a79
513ddbc
2aced6f
c8be71d
dc1361e
501f005
0e7a6c3
be273f1
6310570
eb8cc08
87555c5
d97e05b
81b3b5c
1eb8af5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.gdschongik.gdsc.domain.study.api; | ||
|
||
import com.gdschongik.gdsc.domain.study.application.StudyHistoryService; | ||
import com.gdschongik.gdsc.domain.study.dto.request.RepositoryUpdateRequest; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import jakarta.validation.Valid; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PutMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@Tag(name = "Student Study History", description = "사용자 스터디 수강 이력 API입니다.") | ||
@RestController | ||
@RequestMapping("/study-history") | ||
@RequiredArgsConstructor | ||
public class StudentStudyHistoryController { | ||
|
||
private final StudyHistoryService studyHistoryService; | ||
|
||
@Operation(summary = "레포지토리 입력", description = "레포지토리를 입력합니다. 이미 제출한 과제가 있다면 수정할 수 없습니다.") | ||
@PutMapping("/{studyHistoryId}/repository") | ||
public ResponseEntity<Void> updateRepository( | ||
@PathVariable Long studyHistoryId, @Valid @RequestBody RepositoryUpdateRequest request) { | ||
studyHistoryService.updateRepository(studyHistoryId, request); | ||
return ResponseEntity.ok().build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package com.gdschongik.gdsc.domain.study.application; | ||
|
||
import static com.gdschongik.gdsc.global.common.constant.GithubConstant.*; | ||
import static com.gdschongik.gdsc.global.exception.ErrorCode.*; | ||
|
||
import com.gdschongik.gdsc.domain.member.domain.Member; | ||
import com.gdschongik.gdsc.domain.study.dao.AssignmentHistoryRepository; | ||
import com.gdschongik.gdsc.domain.study.dao.StudyHistoryRepository; | ||
import com.gdschongik.gdsc.domain.study.domain.Study; | ||
import com.gdschongik.gdsc.domain.study.domain.StudyHistory; | ||
import com.gdschongik.gdsc.domain.study.domain.StudyHistoryValidator; | ||
import com.gdschongik.gdsc.domain.study.dto.request.RepositoryUpdateRequest; | ||
import com.gdschongik.gdsc.global.exception.CustomException; | ||
import com.gdschongik.gdsc.global.util.MemberUtil; | ||
import com.gdschongik.gdsc.infra.client.github.GithubClient; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Slf4j | ||
@Service | ||
@RequiredArgsConstructor | ||
public class StudyHistoryService { | ||
|
||
private final MemberUtil memberUtil; | ||
private final GithubClient githubClient; | ||
private final StudyHistoryRepository studyHistoryRepository; | ||
private final AssignmentHistoryRepository assignmentHistoryRepository; | ||
private final StudyHistoryValidator studyHistoryValidator; | ||
|
||
@Transactional | ||
public void updateRepository(Long studyHistoryId, RepositoryUpdateRequest request) { | ||
Member currentMember = memberUtil.getCurrentMember(); | ||
StudyHistory studyHistory = studyHistoryRepository | ||
.findById(studyHistoryId) | ||
.orElseThrow(() -> new CustomException(STUDY_HISTORY_NOT_FOUND)); | ||
Study study = studyHistory.getStudy(); | ||
|
||
boolean isAnyAssignmentSubmitted = | ||
assignmentHistoryRepository.existsSubmittedAssignmentByMemberAndStudy(currentMember, study); | ||
studyHistoryValidator.validateUpdateRepository(isAnyAssignmentSubmitted); | ||
validateRepositoryLink(request.repositoryLink()); | ||
|
||
studyHistory.updateRepositoryLink(request.repositoryLink()); | ||
studyHistoryRepository.save(studyHistory); | ||
|
||
log.info("[StudyHistoryService] 레포지토리 입력: studyHistoryId={}", studyHistory.getId()); | ||
} | ||
|
||
private void validateRepositoryLink(String repositoryLink) { | ||
String ownerRepo = getOwnerRepo(repositoryLink); | ||
githubClient.getRepository(ownerRepo); | ||
} | ||
|
||
private String getOwnerRepo(String repositoryLink) { | ||
int startIndex = repositoryLink.indexOf(GITHUB_DOMAIN) + GITHUB_DOMAIN.length(); | ||
return repositoryLink.substring(startIndex); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.gdschongik.gdsc.domain.study.dao; | ||
|
||
import com.gdschongik.gdsc.domain.member.domain.Member; | ||
import com.gdschongik.gdsc.domain.study.domain.Study; | ||
|
||
public interface AssignmentHistoryCustomRepository { | ||
|
||
boolean existsSubmittedAssignmentByMemberAndStudy(Member member, Study study); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.gdschongik.gdsc.domain.study.dao; | ||
|
||
import static com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionStatus.*; | ||
import static com.gdschongik.gdsc.domain.study.domain.QAssignmentHistory.*; | ||
|
||
import com.gdschongik.gdsc.domain.member.domain.Member; | ||
import com.gdschongik.gdsc.domain.study.domain.Study; | ||
import com.querydsl.core.types.dsl.BooleanExpression; | ||
import com.querydsl.jpa.impl.JPAQueryFactory; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RequiredArgsConstructor | ||
public class AssignmentHistoryCustomRepositoryImpl implements AssignmentHistoryCustomRepository { | ||
|
||
private final JPAQueryFactory queryFactory; | ||
|
||
@Override | ||
public boolean existsSubmittedAssignmentByMemberAndStudy(Member member, Study study) { | ||
Integer fetchOne = queryFactory | ||
.selectOne() | ||
.from(assignmentHistory) | ||
.where(eqMember(member), eqStudy(study), isSubmitted()) | ||
.fetchFirst(); | ||
|
||
return fetchOne != null; | ||
} | ||
|
||
private BooleanExpression eqMember(Member member) { | ||
return member == null ? null : assignmentHistory.member.eq(member); | ||
} | ||
|
||
private BooleanExpression eqStudy(Study study) { | ||
return study == null ? null : assignmentHistory.studyDetail.study.eq(study); | ||
} | ||
|
||
private BooleanExpression isSubmitted() { | ||
return assignmentHistory.submissionStatus.in(FAILURE, SUCCESS); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.gdschongik.gdsc.domain.study.dao; | ||
|
||
import com.gdschongik.gdsc.domain.study.domain.AssignmentHistory; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface AssignmentHistoryRepository | ||
extends JpaRepository<AssignmentHistory, Long>, AssignmentHistoryCustomRepository {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,8 @@ public class StudyHistory extends BaseEntity { | |
@JoinColumn(name = "study_id") | ||
private Study study; | ||
|
||
private String repositoryLink; | ||
|
||
@Builder(access = AccessLevel.PRIVATE) | ||
private StudyHistory(Member mentee, Study study) { | ||
this.mentee = mentee; | ||
|
@@ -43,6 +45,13 @@ public static StudyHistory create(Member mentee, Study study) { | |
return StudyHistory.builder().mentee(mentee).study(study).build(); | ||
} | ||
|
||
/** | ||
* 레포지토리 링크를 업데이트합니다. | ||
*/ | ||
public void updateRepositoryLink(String repositoryLink) { | ||
this.repositoryLink = repositoryLink; | ||
} | ||
|
||
// 데이터 전달 로직 | ||
public boolean isStudyOngoing() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 해당 PR건은 아니지만 올바른 책임의 메서드가 아닌 것 같네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그렇네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ++ todo 달아두겠습니다~ |
||
return study.isStudyOngoing(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.gdschongik.gdsc.domain.study.dto.request; | ||
|
||
import jakarta.validation.constraints.NotBlank; | ||
|
||
public record RepositoryUpdateRequest(@NotBlank String repositoryLink) {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.gdschongik.gdsc.global.common.constant; | ||
|
||
public class GithubConstant { | ||
|
||
public static final String GITHUB_DOMAIN = "github.com/"; | ||
|
||
private GithubConstant() {} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
한번 등록시 변경 불가능하도록 정책 설정하면 좋을듯 합니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분은 피그마에 '과제 최초 제출 시'까지 변경 가능하다고 써있더라고요.
validator에서 과제 제출 이력이 있는지 검증하는 것도 같은 이유입니다!