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] 기존 api 중 폴링으로 인한 변경 반영, studyId로 참여코드 조회 api 구현 #569

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 @@ -45,6 +45,8 @@ public class Participant extends BaseTimeEntity {

private String nickname;

private Boolean isHost;

public Participant(Study study, Member member, String nickname) {
this.study = study;
this.member = member;
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
@@ -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
Expand Up @@ -3,17 +3,21 @@
import harustudy.backend.auth.Authenticated;
import harustudy.backend.auth.dto.AuthMember;
import harustudy.backend.study.dto.CreateStudyRequest;
import harustudy.backend.study.dto.CreateStudyResponse;
import harustudy.backend.study.dto.StudyResponse;
import harustudy.backend.study.dto.StudiesResponse;
import harustudy.backend.study.dto.StudyResponse;
import harustudy.backend.study.service.StudyService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "스터디 관련 기능")
@RequiredArgsConstructor
Expand Down Expand Up @@ -46,16 +50,14 @@ public ResponseEntity<StudiesResponse> findStudiesWithFilter(
@Operation(summary = "스터디 생성")
@ApiResponse(responseCode = "201")
@PostMapping("/api/studies")
public ResponseEntity<CreateStudyResponse> createStudy(
public ResponseEntity<Void> createStudy(
@Authenticated AuthMember authMember,
@RequestBody CreateStudyRequest request
) {
CreateStudyResponse response = studyService.createStudy(request);
return ResponseEntity.created(URI.create("/api/studies/" + response.studyId()))
.body(response);
Long studyId = studyService.createStudy(request);
return ResponseEntity.created(URI.create("/api/studies/" + studyId)).build();
}

// TODO: SSE 이벤트하는 로직으로 변경시 SSE 컨트롤러로 이동할지 고려
@Operation(summary = "다음 스터디 단계로 이동")
@ApiResponse(responseCode = "204")
@PostMapping("/api/studies/{studyId}/next-step")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
import java.time.LocalDateTime;

public record StudyResponse(Long studyId, String name, Integer totalCycle, Integer timePerCycle, Integer currentCycle,
String studyStep, String progress, LocalDateTime createdDateTime) {
String studyStep, String progressStep, LocalDateTime createdDate, LocalDateTime lastModifiedDate) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

progressStep를 쓸지 progress를 쓸지 저희끼리 결정하고 가면 좋을 듯 하네여!(나중에 컨벤션 통일시키려면)

Copy link
Collaborator

@MoonJeWoong MoonJeWoong Sep 25, 2023

Choose a reason for hiding this comment

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

이거 progress로 사용하기로 해서 마코가 merge 하기 전에 수정했던 것 같은데 표현이 혼재되어 있었네용... 저는 개인적으로 step 붙여주는게 통일감 있어서 좋기는 합니다! 그리고 In-Progress 상태에서 단계를 나타낸다는 의미로 progressStep 네이밍도 좋은 것 같아욤


public static final String IN_PROGRESS = "inProgress";

public static StudyResponse from(Study study) {
String roomStep = getRoomStep(study);
String studyStep = getStudyStep(study);
String progressStep = getProgressStep(study);

return new StudyResponse(study.getId(), study.getName(), study.getTotalCycle(), study.getTimePerCycle(),
study.getCurrentCycle(), roomStep, progressStep, study.getCreatedDate());
study.getCurrentCycle(), studyStep, progressStep, study.getCreatedDate(), study.getLastModifiedDate());
}

private static String getRoomStep(Study study) {
private static String getStudyStep(Study study) {
String roomStep;
if (study.isStep(Step.PLANNING) || study.isStep(Step.STUDYING) || study.isStep(Step.RETROSPECT)) {
roomStep = IN_PROGRESS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
import harustudy.backend.member.domain.Member;
import harustudy.backend.member.exception.MemberNotFoundException;
import harustudy.backend.member.repository.MemberRepository;
import harustudy.backend.participantcode.domain.GenerationStrategy;
import harustudy.backend.participantcode.domain.ParticipantCode;
import harustudy.backend.participant.domain.Participant;
import harustudy.backend.participant.repository.ParticipantRepository;
import harustudy.backend.participantcode.domain.GenerationStrategy;
import harustudy.backend.participantcode.domain.ParticipantCode;
import harustudy.backend.participantcode.repository.ParticipantCodeRepository;
import harustudy.backend.study.domain.Study;
import harustudy.backend.study.dto.CreateStudyRequest;
import harustudy.backend.study.dto.CreateStudyResponse;
import harustudy.backend.study.dto.StudyResponse;
import harustudy.backend.study.dto.StudiesResponse;
import harustudy.backend.study.dto.StudyResponse;
import harustudy.backend.study.exception.ParticipantCodeNotFoundException;
import harustudy.backend.study.exception.StudyNotFoundException;
import harustudy.backend.study.repository.StudyRepository;
Expand Down Expand Up @@ -65,21 +64,20 @@ private StudiesResponse findStudyByMemberId(Long memberId) {
return StudiesResponse.from(studies);
}

// TODO: N+1
Copy link
Collaborator

Choose a reason for hiding this comment

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

꼼꼼함... 좋네요 👍

private List<Study> mapToStudies(List<Participant> participants) {
return participants.stream()
.map(Participant::getStudy)
.toList();
}

public CreateStudyResponse createStudy(CreateStudyRequest request) {
public Long createStudy(CreateStudyRequest request) {
Study study = new Study(request.name(), request.totalCycle(),
request.timePerCycle());
Study savedStudy = studyRepository.save(study);
participantCodeRepository.save(generateUniqueCode(savedStudy));

ParticipantCode participantCode = generateUniqueCode(study);
participantCodeRepository.save(participantCode);

return CreateStudyResponse.from(savedStudy, participantCode);
return savedStudy.getId();
}

private ParticipantCode generateUniqueCode(Study study) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@
import harustudy.backend.content.dto.WritePlanRequest;
import harustudy.backend.content.dto.WriteRetrospectRequest;
import harustudy.backend.integration.LoginResponse;
import harustudy.backend.participant.dto.ParticipateStudyRequest;
import harustudy.backend.participant.dto.ParticipantsResponse;
import harustudy.backend.participant.dto.ParticipateStudyRequest;
import harustudy.backend.participantcode.dto.ParticipantCodeResponse;
import harustudy.backend.study.dto.CreateStudyRequest;
import harustudy.backend.study.dto.CreateStudyResponse;
import harustudy.backend.study.dto.StudyResponse;
import harustudy.backend.study.dto.StudiesResponse;
import harustudy.backend.study.dto.StudyResponse;
import jakarta.servlet.http.Cookie;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
Expand Down Expand Up @@ -80,8 +81,7 @@ void setUp() {
@Test
void 회원으로_스터디를_진행한다() throws Exception {
LoginResponse 로그인_정보 = 구글_로그인을_진행한다();
String 참여_코드 = 스터디를_개설한다(로그인_정보);
Long 스터디_아이디 = 스터디를_조회한다(로그인_정보, 참여_코드);
Long 스터디_아이디 = 스터디를_개설한다(로그인_정보);
Copy link
Collaborator

Choose a reason for hiding this comment

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

스터디를 개설하는 사람은 플로우에서 참여 코드가 필요 없어졌군요.
참여하는 사람의 플로우는 참여 코드로 스터디를 조회하는 과정이 필요할텐데 이 시나리오도 나중에 추가되면 좋겠네요.

Copy link
Collaborator

Choose a reason for hiding this comment

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

이거 까먹을 것 같은데 이슈로 만들어둘게용

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

만들까 했는데 남의 참여코드로 참여까지만 하면 되서 전체 플로우 만들기보다 그냥 서비스 테스트정도만 만들었어요!
간단하니까 참여하는 부분까지만 인수조건으로 해서 테스트 추가할게요~

Long 참여자_아이디 = 스터디에_참여한다(로그인_정보, 스터디_아이디);
스터디_상태를_다음_단계로_넘긴다(로그인_정보, 스터디_아이디);
스터디_계획을_작성한다(로그인_정보, 스터디_아이디, 참여자_아이디);
Expand All @@ -106,8 +106,7 @@ void setUp() {
@Test
void 비회원으로_스터디를_진행한다() throws Exception {
LoginResponse 로그인_정보 = 비회원_로그인을_진행한다();
String 참여_코드 = 스터디를_개설한다(로그인_정보);
Long 스터디_아이디 = 스터디를_조회한다(로그인_정보, 참여_코드);
Long 스터디_아이디 = 스터디를_개설한다(로그인_정보);
Long 진행도_아이디 = 스터디에_참여한다(로그인_정보, 스터디_아이디);
스터디_상태를_다음_단계로_넘긴다(로그인_정보, 스터디_아이디);
스터디_계획을_작성한다(로그인_정보, 스터디_아이디, 진행도_아이디);
Expand All @@ -118,6 +117,13 @@ void setUp() {
스터디_종료_후_결과_조회(로그인_정보, 스터디_아이디);
}

@Test
void 비회원으로_타인의_스터디에_참여한다() throws Exception {
String 참여_코드 = 타인의_스터디_참여_코드를_얻는다();
LoginResponse 로그인_정보 = 비회원_로그인을_진행한다();
참여_코드로_스터디_아이디를_얻는다(로그인_정보, 참여_코드);
}

private List<StudyResponse> 회원으로_진행했던_모든_스터디_목록을_조회한다(LoginResponse 로그인_정보)
throws Exception {
long memberId = Long.parseLong(jwtTokenProvider
Expand Down Expand Up @@ -170,7 +176,7 @@ void setUp() {
return new LoginResponse(tokenResponse, refreshToken);
}

private String 스터디를_개설한다(LoginResponse 로그인_정보) throws Exception {
private Long 스터디를_개설한다(LoginResponse 로그인_정보) throws Exception {
CreateStudyRequest request = new CreateStudyRequest("studyName", 1, 20);
String jsonRequest = objectMapper.writeValueAsString(request);
MvcResult result = mockMvc.perform(
Expand All @@ -180,24 +186,11 @@ void setUp() {
.header(HttpHeaders.AUTHORIZATION, 로그인_정보.createAuthorizationHeader()))
.andExpect(status().isCreated())
.andReturn();
String jsonResponse = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
CreateStudyResponse response = objectMapper.readValue(jsonResponse,
CreateStudyResponse.class);
return response.participantCode();
}
String locationHeader = result.getResponse().getHeader(HttpHeaders.LOCATION);

private Long 스터디를_조회한다(LoginResponse 로그인_정보, String 참여_코드) throws Exception {
MvcResult result = mockMvc.perform(
get("/api/studies")
.param("participantCode", 참여_코드)
.header(HttpHeaders.AUTHORIZATION, 로그인_정보.createAuthorizationHeader()))
.andExpect(status().isOk())
.andReturn();
String jsonResponse = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
StudiesResponse responses = objectMapper.readValue(jsonResponse,
StudiesResponse.class);
StudyResponse response = responses.studies().get(0);
return response.studyId();
String[] parsed = locationHeader.split("/");
System.out.println(locationHeader);
return Long.parseLong(parsed[3]);
}

private Long 스터디에_참여한다(LoginResponse 로그인_정보, Long 스터디_아이디) throws Exception {
Expand Down Expand Up @@ -265,4 +258,38 @@ void setUp() {

assertThat(jsonResponse.participants()).hasSize(1);
}

private String 타인의_스터디_참여_코드를_얻는다() throws Exception {
LoginResponse 로그인_정보 = 비회원_로그인을_진행한다();
Long 스터디_아이디 = 스터디를_개설한다(로그인_정보);
return 스터디_아이디로_참여_코드를_얻는다(로그인_정보, 스터디_아이디);
}

private String 스터디_아이디로_참여_코드를_얻는다(LoginResponse 로그인_정보, Long 스터디_아이디) throws Exception {
MvcResult result = mockMvc.perform(get("/api/participant-codes")
.param("studyId", 스터디_아이디.toString())
.accept(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, 로그인_정보.createAuthorizationHeader()))
.andExpect(status().isOk())
.andReturn();

String response = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
ParticipantCodeResponse jsonResponse = objectMapper.readValue(response,
ParticipantCodeResponse.class);

assertThat(jsonResponse.participantCode()).hasSize(6);
return jsonResponse.participantCode();
}

private void 참여_코드로_스터디_아이디를_얻는다(LoginResponse 로그인_정보, String 참여_코드) throws Exception {
MvcResult result = mockMvc.perform(get("/api/studies")
.param("participantCode", 참여_코드)
.accept(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, 로그인_정보.createAuthorizationHeader()))
.andExpect(status().isOk())
.andReturn();
String response = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
Assertions.assertDoesNotThrow(() -> objectMapper.readValue(response,
StudyResponse.class));
}
}
Loading