Skip to content

Commit

Permalink
[BE] 폴링 기능 구현 (#584)
Browse files Browse the repository at this point in the history
* feat: Polling 관련 패키지 구성

* [BE] 기존 api 중 폴링으로 인한 변경 반영, studyId로 참여코드 조회 api 구현 (#569)

* feat: progress 삭제 및 study 정보 변경 관련 api 수정

* feat: studyId로 참여 코드 조회 api 구현

* fix: CachingFilter를 /api/participantCodes 에 적용

* feat: 스터디 아이디로 참여 코드 조회 시나리오 테스트 추가

* fix: 참여 코드 관련 필터 로직 수정

* refactor: 스터디 생성 시 Location 헤더로만 응답을 반환하도록 변경

* [BE] 대기방 페이지 폴링 기능 구현하기 (#575)

* feat: 대기방 폴링 api 구현

* refactor: 스터디 참여 로직 수정

* test: 참여 관련 테스트 추가

* refactor: 생성자 체이닝 사용

* refactor: import 와일드카드 제거

* refactor: 메서드 네이밍 변경

* test: 메서드 네이밍 참여를 참여자 정보로 수정

* test: 메서드 네이밍 폴링 키워드 제거

* refactor: dto 변환 로직 dto로 이동

* refactor: 개행 제거

* refactor: 정적 팩터리 메서드 도입 및 생성자 제거로 인한 테스트 수정사항 반영

* test: expected를 given으로 이동

* refactor: 정적 팩터리 메서드 네이밍 수정

* test: expected를 given으로 이동

* [BE] 이미 시작한 스터디 신규 참여 차단 로직 구현 (#578)

* feat: 이미 시작한 스터디 참여 시 에러 반환 로직 구현

* refactor: assertThrows -> assertThatThrownBy

다른 테스트와 동일한 방식으로 변경

* [BE] 진행 페이지 폴링 기능 구현하기 (#577)

* feat: polling API 구현을 위한 view 패키지 및 기본 컨트롤러, 서비스 클래스 생성

* feat: PollingController 구현

* feat: 폴링 서비스 로직 및 테스트 구현

* feat: 스터디 상태 값만 조회하도록 구현

* feat: controller 단에 authMember 추가

* feat: 기타 수정 사항 반영

* feat: 스터디에서 step 조회 시 Enum으로 조회하도록 수정

* feat: StudyRepository 메서드 네이밍 수정

* style: 잘못된 개행 수정

* fix: resolve merge conflict

---------

Co-authored-by: aak2075 <[email protected]>

* [BE] 제출한 인원 조회하는 기능 구현하기 (#579)

* feat: 제출 인원 조회 기능 및 테스트 구현

* test: 스터디 종료 상태 테스트 케이스 추가

* refactor: DTO에서 정적 팩토리 메소드를 사용하도록 변경

* refactor: Controller -> RestController로 변경

* chore: ExceptionMapper Exception 추가

* chore: 클래스명 수정

* fix: resolve merge conflict

---------

Co-authored-by: aak2075 <[email protected]>

---------

Co-authored-by: Gyuseong Lee <[email protected]>
Co-authored-by: hiiro <[email protected]>
Co-authored-by: teo <[email protected]>
  • Loading branch information
4 people authored Sep 26, 2023
1 parent 7ddcd8e commit c624ad5
Show file tree
Hide file tree
Showing 41 changed files with 1,096 additions and 312 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
import harustudy.backend.participant.exception.ParticipantNotBelongToStudyException;
import harustudy.backend.study.exception.ParticipantCodeExpiredException;
import harustudy.backend.study.exception.ParticipantCodeNotFoundException;
import harustudy.backend.study.exception.StudyAlreadyStartedException;
import harustudy.backend.study.exception.StudyNameLengthException;
import harustudy.backend.study.exception.TimePerCycleException;
import harustudy.backend.study.exception.TotalCycleException;
import harustudy.backend.study.exception.StudyNotFoundException;
import harustudy.backend.polling.exception.CannotSeeSubmittersException;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -69,6 +72,10 @@ private static void setUpStudyException() {
ExceptionSituation.of("시간 당 사이클 횟수가 적절하지 않습니다.", BAD_REQUEST, 1305));
mapper.put(TotalCycleException.class,
ExceptionSituation.of("총 사이클 횟수가 적절하지 않습니다.", BAD_REQUEST, 1306));
mapper.put(StudyAlreadyStartedException.class,
ExceptionSituation.of("이미 시작된 스터디입니다.", BAD_REQUEST, 1307));
mapper.put(CannotSeeSubmittersException.class,
ExceptionSituation.of("해당 단계에서는 제출 여부를 조회할 수 없습니다.", BAD_REQUEST, 1308));
}

private static void setUpAuthenticationException() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class FilterConfig {
public FilterRegistrationBean<CachingFilter> contentCachingFilter(){
FilterRegistrationBean<CachingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CachingFilter());
registrationBean.addUrlPatterns("/api/studies/*", "/api/temp/*", "/api/auth/*", "/api/me/*");
registrationBean.addUrlPatterns("/api/participant-codes/*", "/api/studies/*", "/api/temp/*", "/api/auth/*", "/api/me/*");
registrationBean.setOrder(1);
registrationBean.setName("CachingFilter");
return registrationBean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ public boolean hasSameCycleWith(Study study) {
return cycle.equals(study.getCurrentCycle());
}

public boolean hasEmptyPlan() {
return plan.isEmpty();
public boolean isPlanWritten() {
return !plan.isEmpty();
}

public boolean isRetrospectWritten() {
return !retrospect.isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ private void validateStudyIsRetrospect(Study study) {
}

private void validateIsPlanFilled(Content recentContent) {
if (recentContent.hasEmptyPlan()) {
if (!recentContent.isPlanWritten()) {
throw new StudyStepException();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;

import java.util.ArrayList;
import java.util.List;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

// TODO: 연관관계 편의 메소드 생성(memberContents에 넣는)
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand All @@ -45,21 +44,30 @@ public class Participant extends BaseTimeEntity {

private String nickname;

public Participant(Study study, Member member, String nickname) {
private Boolean isHost;

private Participant(Study study, Member member, String nickname, Boolean isHost) {
this.study = study;
this.member = member;
this.nickname = nickname;
this.isHost = isHost;
}

public static Participant instantiateParticipantWithContents(Study study, Member member, String nickname) {
validateNicknameLength(nickname);
Participant participant = new Participant(study, member, nickname, study.isEmptyParticipants());
study.addParticipant(participant);
participant.generateContents(study.getTotalCycle());
return participant;
}

private void validateNicknameLength(String nickname) {
private static void validateNicknameLength(String nickname) {
if (nickname.length() < 1 || nickname.length() > 10) {
throw new NicknameLengthException();
}
}

public void generateContents(int totalCycle) {
private void generateContents(int totalCycle) {
for (int cycle = 1; cycle <= totalCycle; cycle++) {
Content content = new Content(this, cycle);
contents.add(content);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import harustudy.backend.participant.domain.Participant;

public record ParticipantResponse(Long participantId, String nickname) {
public record ParticipantResponse(Long participantId, String nickname, Boolean isHost) {

public static ParticipantResponse from(Participant participant) {
return new ParticipantResponse(participant.getId(), participant.getNickname());
return new ParticipantResponse(participant.getId(), participant.getNickname(), participant.getIsHost());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ Optional<Participant> findByStudyAndMember(Study study,
List<Participant> findAllByStudyFetchMember(
@Param("study") Study study);


List<Participant> findByMember(Member member);

List<Participant> findByStudy(Study study);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@
import harustudy.backend.member.domain.Member;
import harustudy.backend.member.repository.MemberRepository;
import harustudy.backend.participant.domain.Participant;
import harustudy.backend.participant.domain.Step;
import harustudy.backend.participant.dto.ParticipateStudyRequest;
import harustudy.backend.participant.dto.ParticipantResponse;
import harustudy.backend.participant.dto.ParticipantsResponse;
import harustudy.backend.participant.exception.ParticipantNotFoundException;
import harustudy.backend.participant.dto.ParticipateStudyRequest;
import harustudy.backend.participant.exception.ParticipantNotBelongToStudyException;
import harustudy.backend.participant.exception.ParticipantNotFoundException;
import harustudy.backend.participant.repository.ParticipantRepository;
import harustudy.backend.study.domain.Study;
import harustudy.backend.study.exception.StudyAlreadyStartedException;
import harustudy.backend.study.repository.StudyRepository;
import jakarta.annotation.Nullable;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

@RequiredArgsConstructor
@Transactional
@Service
Expand Down Expand Up @@ -104,13 +108,12 @@ private ParticipantsResponse getParticipantsResponseWithMemberFilter(
return ParticipantsResponse.from(List.of(response));
}

public Long participateStudy(AuthMember authMember, Long studyId,
ParticipateStudyRequest request) {
public Long participateStudy(AuthMember authMember, Long studyId, ParticipateStudyRequest request) {
Member member = memberRepository.findByIdIfExists(request.memberId());
validateIsSameMemberId(authMember, request.memberId());
Study study = studyRepository.findByIdIfExists(studyId);
Participant participant = new Participant(study, member, request.nickname());
participant.generateContents(study.getTotalCycle());
validateIsWaiting(study);
Participant participant = Participant.instantiateParticipantWithContents(study, member, request.nickname());
Participant saved = participantRepository.save(participant);
return saved.getId();
}
Expand All @@ -121,6 +124,12 @@ private void validateIsSameMemberId(AuthMember authMember, Long memberId) {
}
}

private void validateIsWaiting(Study study) {
if (!study.isStep(Step.WAITING)) {
throw new StudyAlreadyStartedException();
}
}

private void validateParticipantIsRelatedWith(
Participant participant, AuthMember authMember, Study study
) {
Expand All @@ -135,8 +144,7 @@ private void validateMemberOwns(Participant participant, AuthMember authMember)
}
}

private void validateParticipantIsIncludedIn(Study study,
Participant participant) {
private void validateParticipantIsIncludedIn(Study study, Participant participant) {
if (participant.isNotIncludedIn(study)) {
throw new ParticipantNotBelongToStudyException();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package harustudy.backend.participantcode.controller;

import harustudy.backend.auth.Authenticated;
import harustudy.backend.auth.dto.AuthMember;
import harustudy.backend.participantcode.dto.ParticipantCodeResponse;
import harustudy.backend.participantcode.service.ParticipantCodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "참여 코드 관련 기능")
@RequiredArgsConstructor
@RestController
public class ParticipantCodeController {

private final ParticipantCodeService participantCodeService;

@Operation(summary = "스터디 아이디로 참여 코드 조회")
@GetMapping("/api/participant-codes")
public ResponseEntity<ParticipantCodeResponse> findParticipantCodeByStudyId(
@Authenticated AuthMember authMember,
@RequestParam Long studyId
) {
ParticipantCodeResponse response = participantCodeService.findParticipantCodeByStudyId(
studyId);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package harustudy.backend.participantcode.dto;

import harustudy.backend.participantcode.domain.ParticipantCode;

public record ParticipantCodeResponse(String participantCode) {

public static ParticipantCodeResponse from(ParticipantCode participantCode) {
return new ParticipantCodeResponse(participantCode.getCode());
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package harustudy.backend.participantcode.repository;

import harustudy.backend.participantcode.domain.ParticipantCode;
import harustudy.backend.study.domain.Study;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ParticipantCodeRepository extends JpaRepository<ParticipantCode, Long> {

Optional<ParticipantCode> findByCode(String code);

Optional<ParticipantCode> findByStudy(Study study);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package harustudy.backend.participantcode.service;

import harustudy.backend.participantcode.domain.ParticipantCode;
import harustudy.backend.participantcode.dto.ParticipantCodeResponse;
import harustudy.backend.participantcode.repository.ParticipantCodeRepository;
import harustudy.backend.study.domain.Study;
import harustudy.backend.study.exception.ParticipantCodeNotFoundException;
import harustudy.backend.study.repository.StudyRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Transactional
@Service
public class ParticipantCodeService {

private final StudyRepository studyRepository;
private final ParticipantCodeRepository participantCodeRepository;

@Transactional(readOnly = true)
public ParticipantCodeResponse findParticipantCodeByStudyId(Long studyId) {
Study study = studyRepository.findByIdIfExists(studyId);
ParticipantCode participantCode = participantCodeRepository.findByStudy(study)
.orElseThrow(ParticipantCodeNotFoundException::new);
return ParticipantCodeResponse.from(participantCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package harustudy.backend.polling.controller;

import harustudy.backend.auth.Authenticated;
import harustudy.backend.auth.dto.AuthMember;
import harustudy.backend.polling.dto.ProgressResponse;
import harustudy.backend.polling.dto.SubmittersResponse;
import harustudy.backend.polling.dto.WaitingResponse;
import harustudy.backend.polling.service.PollingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "폴링 관련 기능")
@RequiredArgsConstructor
@RestController
public class PollingController {

private final PollingService pollingService;

@Operation(summary = "진행 페이지 폴링")
@GetMapping("/api/progress")
public ResponseEntity<ProgressResponse> progressPolling(
@Authenticated AuthMember authMember, @RequestParam Long studyId
) {
ProgressResponse progressResponse = pollingService.pollProgress(studyId);
return ResponseEntity.ok(progressResponse);
}
@GetMapping("/api/waiting")
public ResponseEntity<WaitingResponse> pollWaiting(@Authenticated AuthMember authMember, @RequestParam Long studyId) {
WaitingResponse waitingResponse = pollingService.pollWaiting(studyId);
return ResponseEntity.ok(waitingResponse);
}

@Operation(summary = "스터디원 별 제출 여부 조회")
@GetMapping("/api/submitted")
public ResponseEntity<SubmittersResponse> findSubmitters(
@Authenticated AuthMember authMember,
@RequestParam Long studyId
) {
return ResponseEntity.ok(pollingService.findSubmitters(studyId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package harustudy.backend.polling.dto;

import harustudy.backend.participant.domain.Step;

public record ProgressResponse(String progressStep) {

public static ProgressResponse from(Step progressStep) {
return new ProgressResponse(progressStep.name().toLowerCase());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package harustudy.backend.polling.dto;

public record SubmitterResponse(String nickname, Boolean submitted) {

public static SubmitterResponse of(String nickname, Boolean submitted) {
return new SubmitterResponse(nickname, submitted);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package harustudy.backend.polling.dto;

import java.util.List;

public record SubmittersResponse(List<SubmitterResponse> status) {

public static SubmittersResponse from(List<SubmitterResponse> submitterResponses) {
return new SubmittersResponse(submitterResponses);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package harustudy.backend.polling.dto;

import harustudy.backend.participant.domain.Participant;
import harustudy.backend.participant.domain.Step;
import harustudy.backend.participant.dto.ParticipantResponse;
import harustudy.backend.study.domain.Study;

import java.util.List;

import static harustudy.backend.study.dto.StudyResponse.IN_PROGRESS;

public record WaitingResponse(String studyStep, List<ParticipantResponse> participants) {

public static WaitingResponse of(Study study, List<Participant> participants) {
return new WaitingResponse(getRoomStep(study), mapToParticipantResponses(participants));
}

private static String getRoomStep(Study study) {
String roomStep;
if (study.isStep(Step.PLANNING) || study.isStep(Step.STUDYING) || study.isStep(Step.RETROSPECT)) {
roomStep = IN_PROGRESS;
} else {
roomStep = study.getStep().name().toLowerCase();
}
return roomStep;
}

private static List<ParticipantResponse> mapToParticipantResponses(List<Participant> participants) {
return participants.stream()
.map(ParticipantResponse::from)
.toList();
}
}
Loading

0 comments on commit c624ad5

Please sign in to comment.