Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[BE] issue122: 스터디 참여 기능 #132

Merged
merged 29 commits into from Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8de6562
refactor: 네이밍 변경
tco0427 Jul 26, 2022
40a88b4
feat: StudyService 구현 및 테스트
tco0427 Jul 26, 2022
593a4d3
Merge branch 'develop' of https://github.com/woowacourse-teams/2022-m…
tco0427 Jul 26, 2022
b0dc328
feat: Participants 검증 로직 및 참여자 추가 로직 구현
tco0427 Jul 26, 2022
0f79069
feat: Details `CLOSE` 확인 로직 추가
tco0427 Jul 26, 2022
bbc410a
feat: Period 모집 기간 확인 로직 추가
tco0427 Jul 26, 2022
9f0a622
feat: Study 참여 로직 추가
tco0427 Jul 26, 2022
3df6cba
feat: StudyService 회원 참여 기능 구현 및 생성 테스트 로직 추가
tco0427 Jul 26, 2022
5b6a5c5
feat: StudyController 구현 및 테스트
tco0427 Jul 26, 2022
40a7434
fix: 스터디 생성 테스트 오류 수정
tco0427 Jul 26, 2022
47b49dd
fix: EnrollmentEndDate 는 null 일수 있으므로 이를 고려하여 검증 로직 수정
tco0427 Jul 27, 2022
63a4378
feat: 인수 테스트 작성
tco0427 Jul 27, 2022
9291dd0
fix: ParticipantsTest 수정
tco0427 Jul 27, 2022
0634073
fix: owner는 participants 가 아니므로 제거
tco0427 Jul 27, 2022
72274e1
fix: isInvalidMemberSize `스터디 최대 인원수` 가 없는 경우도 반영하도록 수정
tco0427 Jul 28, 2022
b1ec72f
refactor: 마감일자 검증 로직 가독성 있게 변경
tco0427 Jul 28, 2022
90e9ec5
refactor: assertThatThrownBy().isInstnaceOf() 로 예외 타입 체크
tco0427 Jul 28, 2022
e482a95
refactor: `isAlreadyParticipation` 메소드 내부 로직 메소드 분리
tco0427 Jul 28, 2022
f7f8bd3
refactor: 의미가 분명한 네이밍으로 변경
tco0427 Jul 28, 2022
5822471
fix: 참여자에서 방장은 제외하도록 수정
tco0427 Jul 28, 2022
78aafb4
fix: 참여자수를 확인하는 테스트 코드 수정
tco0427 Jul 28, 2022
574b6ad
refactor: checkParticipating 캡슐화
tco0427 Jul 28, 2022
6d34aee
refactor: 스터디 참여 검증 로직 개선
tco0427 Jul 28, 2022
8786486
test: 스터디장은 스터디에 참여할 수 없다.
tco0427 Jul 28, 2022
d9c0b25
refactor: Study 추상화 및 예외 네이밍 변경
tco0427 Jul 29, 2022
eed9d4e
refactor: Boolean 네이밍 변경
tco0427 Jul 29, 2022
4875818
refactor: 메소드명 동사로 변경
tco0427 Jul 29, 2022
7b78036
refactor: participate 메소드 수정
tco0427 Jul 29, 2022
4dce039
fix: 깨지는 테스트 수정
tco0427 Jul 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,10 +1,12 @@
package com.woowacourse.moamoa.common.advice;

import com.woowacourse.moamoa.common.exception.UnauthorizedException;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;

import com.woowacourse.moamoa.common.advice.response.ErrorResponse;
import com.woowacourse.moamoa.common.exception.InvalidFormatException;
import com.woowacourse.moamoa.common.exception.UnauthorizedException;
import com.woowacourse.moamoa.study.domain.exception.InvalidPeriodException;
import org.springframework.http.HttpStatus;
import com.woowacourse.moamoa.study.service.exception.FailureParticipationException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
Expand All @@ -14,14 +16,15 @@ public class CommonControllerAdvice {

@ExceptionHandler({
InvalidFormatException.class,
InvalidPeriodException.class
InvalidPeriodException.class,
FailureParticipationException.class
})
public ResponseEntity<ErrorResponse> handleBadRequest(final Exception e) {
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
}

@ExceptionHandler
public ResponseEntity<Void> handleUnauthorized(final UnauthorizedException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
return ResponseEntity.status(UNAUTHORIZED).build();
}
}
Expand Up @@ -6,10 +6,6 @@
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import com.woowacourse.moamoa.study.domain.Study;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.OneToMany;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand Down
@@ -1,31 +1,40 @@
package com.woowacourse.moamoa.study.controller;

import com.woowacourse.moamoa.auth.config.AuthenticationPrincipal;
import com.woowacourse.moamoa.study.service.request.CreateStudyRequest;
import com.woowacourse.moamoa.study.domain.Study;
import com.woowacourse.moamoa.study.service.CreateStudyService;
import com.woowacourse.moamoa.study.service.StudyService;
import com.woowacourse.moamoa.study.service.request.CreatingStudyRequest;
import java.net.URI;
import javax.validation.Valid;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
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
@RequiredArgsConstructor
@Getter
@RequestMapping("/api/studies")
public class StudyController {

private final CreateStudyService createStudyService;
private final StudyService studyService;

@PostMapping("/api/studies")
@PostMapping
public ResponseEntity<Void> createStudy(
@AuthenticationPrincipal final Long githubId,
@Valid @RequestBody(required = false) final CreateStudyRequest createStudyRequest
@Valid @RequestBody(required = false) final CreatingStudyRequest creatingStudyRequest
) {
final Study study = createStudyService.createStudy(githubId, createStudyRequest);
final Study study = studyService.createStudy(githubId, creatingStudyRequest);
return ResponseEntity.created(URI.create("/api/studies/" + study.getId())).build();
}

@PostMapping("/{study-id}")
public ResponseEntity<Void> participateStudy(@AuthenticationPrincipal final Long githubId,
@PathVariable("study-id") final Long studyId
) {
studyService.participateStudy(githubId, studyId);
return ResponseEntity.ok().build();
}
}
@@ -1,12 +1,18 @@
package com.woowacourse.moamoa.study.domain;

import static lombok.AccessLevel.PROTECTED;

import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import lombok.NoArgsConstructor;

@Embeddable
@NoArgsConstructor(access = PROTECTED)
public class Details {

private static final String CLOSE = "CLOSE";

@Column(nullable = false)
private String title;

Expand All @@ -22,9 +28,6 @@ public class Details {
@Column(nullable = false)
private String description;

protected Details() {
}

public Details(final String title, final String excerpt, final String thumbnail, final String status,
final String description) {
this.title = title;
Expand All @@ -34,6 +37,10 @@ public Details(final String title, final String excerpt, final String thumbnail,
this.description = description;
}

public boolean isCloseStatus() {
return status.equals(CLOSE);
}

@Override
public boolean equals(final Object o) {
if (this == o) {
Expand Down
Expand Up @@ -2,10 +2,10 @@

import static lombok.AccessLevel.PROTECTED;

import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
Expand All @@ -15,25 +15,9 @@
@NoArgsConstructor(access = PROTECTED)
@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class Participant {

@Column(name = "member_id", nullable = false)
private Long memberId;

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Participant that = (Participant) o;
return Objects.equals(memberId, that.memberId);
}

@Override
public int hashCode() {
return Objects.hash(memberId);
}
}
@@ -1,18 +1,23 @@
package com.woowacourse.moamoa.study.domain;

import static lombok.AccessLevel.PROTECTED;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Embeddable
@ToString
@NoArgsConstructor(access = PROTECTED)
public class Participants {

@Column(name = "current_member_count")
Expand All @@ -26,13 +31,10 @@ public class Participants {

@ElementCollection
@CollectionTable(name = "study_member", joinColumns = @JoinColumn(name = "study_id"))
private List<Participant> participants = new ArrayList<>();

protected Participants() {
}
private Set<Participant> participants = new HashSet<>();

public Participants(final Integer size, final Integer max,
final List<Participant> participants, Long ownerId) {
final Set<Participant> participants, Long ownerId) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

중복된 값 X 👍 명확해서 좋아요!

this.size = size;
this.max = max;
this.participants = participants;
Expand All @@ -43,8 +45,38 @@ public List<Participant> getParticipants() {
return new ArrayList<>(participants);
}

public static Participants createByMaxSizeAndOwnerId(final Integer maxSize, Long id) {
return new Participants(1, maxSize, new ArrayList<>(), id);
public static Participants createByMaxSizeAndOwnerId(final Integer maxSize, Long ownerId) {
return new Participants(1, maxSize, new HashSet<>(), ownerId);
}

public int getCurrentMemberSize() {
return size;
}

void participate(final Participant participant) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

participants.add(participant);
size = size + 1;
}

boolean isImpossibleParticipation(Long memberId) {
return isInvalidMemberSize() || isAlreadyParticipation(memberId);
}

private boolean isInvalidMemberSize() {
return max != null && max <= size;
}

private boolean isAlreadyParticipation(final Long memberId) {
final Participant participant = new Participant(memberId);
return isOwner(memberId) || isParticipated(participant);
}

private boolean isOwner(final Long memberId) {
return Objects.equals(memberId, ownerId);
}

private boolean isParticipated(final Participant participant) {
return participants.contains(participant);
}

@Override
Expand All @@ -61,7 +93,7 @@ public boolean equals(final Object o) {
}

@Override
public int hashCode() {
public int hashCode() {
return Objects.hash(size, max, ownerId, participants);
}
}
@@ -1,13 +1,19 @@
package com.woowacourse.moamoa.study.domain;

import static lombok.AccessLevel.PROTECTED;

import com.woowacourse.moamoa.study.domain.exception.InvalidPeriodException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Embeddable
@Getter
@NoArgsConstructor(access = PROTECTED)
public class Period {

private LocalDate enrollmentEndDate;
Expand All @@ -17,20 +23,34 @@ public class Period {

private LocalDate endDate;

public Period() {
}

public Period(final LocalDate enrollmentEndDate, final LocalDate startDate, final LocalDate endDate) {
if ((endDate != null && startDate.isAfter(endDate)) || (enrollmentEndDate != null && endDate != null && enrollmentEndDate.isAfter(endDate))) {
throw new InvalidPeriodException();
}
validatePeriod(enrollmentEndDate, startDate, endDate);
this.enrollmentEndDate = enrollmentEndDate;
this.startDate = startDate;
this.endDate = endDate;
}

public boolean isBefore(final LocalDateTime createAt) {
return startDate.isBefore(createAt.toLocalDate()) || (enrollmentEndDate != null && enrollmentEndDate.isBefore(createAt.toLocalDate()));
private void validatePeriod(final LocalDate enrollmentEndDate, final LocalDate startDate, final LocalDate endDate) {
if (isImproperStudyDate(startDate, endDate) || isImproperEnrollmentEndDate(enrollmentEndDate, endDate)) {
throw new InvalidPeriodException();
}
}

private boolean isImproperStudyDate(final LocalDate startDate, final LocalDate endDate) {
return endDate != null && startDate.isAfter(endDate);
}

private boolean isImproperEnrollmentEndDate(final LocalDate enrollmentEndDate, final LocalDate endDate) {
return enrollmentEndDate != null && endDate != null && enrollmentEndDate.isAfter(endDate);
}

boolean isBefore(final LocalDateTime createAt) {
return startDate.isBefore(createAt.toLocalDate()) || (enrollmentEndDate != null && enrollmentEndDate.isBefore(
createAt.toLocalDate()));
}

boolean isCloseEnrollment() {
return enrollmentEndDate != null && enrollmentEndDate.isBefore(LocalDate.now());
}

@Override
Expand Down
Expand Up @@ -4,6 +4,7 @@
import static lombok.AccessLevel.PROTECTED;

import com.woowacourse.moamoa.study.domain.exception.InvalidPeriodException;
import com.woowacourse.moamoa.study.service.exception.FailureParticipationException;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Embedded;
Expand Down Expand Up @@ -61,4 +62,12 @@ private void validatePeriod(final Period period) {
throw new InvalidPeriodException();
}
}

public void participate(final Long memberId) {
if (details.isCloseStatus() || period.isCloseEnrollment() || participants.isImpossibleParticipation(memberId)) {
throw new FailureParticipationException();
}

participants.participate(new Participant(memberId));
}
}