Skip to content

Commit

Permalink
Merge branch 'BE/develop' into BE/feature/260
Browse files Browse the repository at this point in the history
  • Loading branch information
yujamint authored Aug 9, 2023
2 parents 7e735fd + 58e87e6 commit 798f096
Show file tree
Hide file tree
Showing 28 changed files with 326 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import com.yigongil.backend.domain.member.Member;
import com.yigongil.backend.domain.member.MemberRepository;
import com.yigongil.backend.domain.member.Nickname;
import com.yigongil.backend.domain.study.Study;
import com.yigongil.backend.domain.studymember.StudyMember;
import com.yigongil.backend.domain.studymember.StudyMemberRepository;
import com.yigongil.backend.exception.MemberNotFoundException;
import com.yigongil.backend.request.ProfileUpdateRequest;
import com.yigongil.backend.response.FinishedStudyResponse;
import com.yigongil.backend.response.NicknameValidationResponse;
import com.yigongil.backend.response.ProfileResponse;
import java.util.List;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -89,4 +91,10 @@ private int calculateNumberOfSuccessRounds(Member member) {
public void update(Member member, ProfileUpdateRequest request) {
member.updateProfile(request.nickname(), request.introduction());
}

@Transactional(readOnly = true)
public NicknameValidationResponse existsByNickname(String nickname) {
boolean exists = memberRepository.existsByNickname(new Nickname(nickname));
return new NicknameValidationResponse(exists);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.yigongil.backend.application;

import static com.yigongil.backend.domain.study.PageStrategy.CREATED_AT_DESC;
import static com.yigongil.backend.domain.study.PageStrategy.ID_DESC;

import com.yigongil.backend.application.studyevent.StudyStartedEvent;
import com.yigongil.backend.domain.member.Member;
Expand All @@ -15,7 +15,7 @@
import com.yigongil.backend.exception.ApplicantAlreadyExistException;
import com.yigongil.backend.exception.ApplicantNotFoundException;
import com.yigongil.backend.exception.StudyNotFoundException;
import com.yigongil.backend.request.StudyCreateRequest;
import com.yigongil.backend.request.StudyUpdateRequest;
import com.yigongil.backend.response.MyStudyResponse;
import com.yigongil.backend.response.RecruitingStudyResponse;
import com.yigongil.backend.response.StudyDetailResponse;
Expand Down Expand Up @@ -49,7 +49,7 @@ public StudyService(
}

@Transactional
public Long create(Member member, StudyCreateRequest request) {
public Long create(Member member, StudyUpdateRequest request) {
Study study = Study.initializeStudyOf(
request.name(),
request.introduction(),
Expand All @@ -76,9 +76,25 @@ public Long create(Member member, StudyCreateRequest request) {

@Transactional(readOnly = true)
public List<RecruitingStudyResponse> findRecruitingStudies(int page) {
Pageable pageable = PageRequest.of(page, CREATED_AT_DESC.getSize(), CREATED_AT_DESC.getSort());
Pageable pageable = PageRequest.of(page, ID_DESC.getSize(), ID_DESC.getSort());

Page<Study> studies = studyRepository.findAllByProcessingStatus(ProcessingStatus.RECRUITING, pageable);
return toRecruitingStudyResponse(studies);

}

@Transactional(readOnly = true)
public List<RecruitingStudyResponse> findRecruitingStudiesWithSearch(int page, String word) {
Pageable pageable = PageRequest.of(page, ID_DESC.getSize(), ID_DESC.getSort());
Page<Study> studies = studyRepository.findAllByProcessingStatusAndNameContainingIgnoreCase(
ProcessingStatus.RECRUITING,
word,
pageable
);
return toRecruitingStudyResponse(studies);
}

private List<RecruitingStudyResponse> toRecruitingStudyResponse(Page<Study> studies) {
return studies.get()
.map(RecruitingStudyResponse::from)
.toList();
Expand Down Expand Up @@ -237,6 +253,20 @@ public void startStudy(Member member, Long studyId) {
publisher.publishEvent(new StudyStartedEvent(study));
}

@Transactional
public void update(Member member, Long studyId, StudyUpdateRequest request) {
Study study = findStudyById(studyId);
study.updateInformation(
member,
request.name(),
request.numberOfMaximumMembers(),
request.startAt().atStartOfDay(),
request.totalRoundCount(),
request.periodOfRound(),
request.introduction()
);
}

private Study findStudyById(Long studyId) {
return studyRepository.findById(studyId)
.orElseThrow(() -> new StudyNotFoundException("해당 스터디를 찾을 수 없습니다", studyId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public void addInterceptors(InterceptorRegistry registry) {
.addPathPatterns("/v1/**")
.excludePathPatterns("/v1/login/**")
.excludePathPatterns("/v1/members/{id:[0-9]\\d*}")
.excludePathPatterns("/v1/studies/recruiting")
.excludePathPatterns("/v1/members/exists")
.excludePathPatterns("/v1/studies/recruiting/**")
.order(2);

registry.addInterceptor(loggingInterceptor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ public interface MemberRepository extends Repository<Member, Long> {
on r.id = :id
""")
List<Member> findMembersByRoundId(@Param("id") Long id);

boolean existsByNickname(Nickname nickname);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@Getter
public enum PageStrategy {

CREATED_AT_DESC(Constants.PAGE_SIZE, Sort.by("createdAt").descending());
ID_DESC(Constants.PAGE_SIZE, Sort.by("id").descending());

private final int size;
private final Sort sort;
Expand Down
42 changes: 31 additions & 11 deletions backend/src/main/java/com/yigongil/backend/domain/study/Study.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,17 +179,6 @@ public void addMember(Member member) {
}
}

private void validateStudyProcessingStatus() {
if (!isRecruiting()) {
throw new InvalidProcessingStatusException("모집 중인 스터디가 아니기 때문에 신청을 수락할 수 없습니다.",
processingStatus.name());
}
}

public boolean isRecruiting() {
return this.processingStatus == ProcessingStatus.RECRUITING;
}

private void validateMemberSize() {
if (sizeOfCurrentMembers() >= numberOfMaximumMembers) {
throw new InvalidMemberSizeException("스터디 정원이 가득 찼습니다.", numberOfMaximumMembers);
Expand Down Expand Up @@ -257,4 +246,35 @@ public String findPeriodOfRoundToString() {
public Member getMaster() {
return currentRound.getMaster();
}

public void updateInformation(
Member member,
String name,
Integer numberOfMaximumMembers,
LocalDateTime startAt,
Integer totalRoundCount,
String periodOfRound,
String introduction
) {
validateMaster(member);
validateStudyProcessingStatus();
this.name = name;
this.numberOfMaximumMembers = numberOfMaximumMembers;
this.startAt = startAt;
this.totalRoundCount = totalRoundCount;
this.periodOfRound = PeriodUnit.getPeriodNumber(periodOfRound);
this.periodUnit = PeriodUnit.getPeriodUnit(periodOfRound);
this.introduction = introduction;
}

private void validateStudyProcessingStatus() {
if (!isRecruiting()) {
throw new InvalidProcessingStatusException("현재 스터디의 상태가 모집중이 아닙니다.",
processingStatus.name());
}
}

public boolean isRecruiting() {
return this.processingStatus == ProcessingStatus.RECRUITING;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public interface StudyRepository extends Repository<Study, Long> {

Page<Study> findAllByProcessingStatus(ProcessingStatus processingStatus, Pageable pageable);

Page<Study> findAllByProcessingStatusAndNameContainingIgnoreCase(ProcessingStatus processingStatus, String word, Pageable pageable);

List<Study> findAllByProcessingStatus(ProcessingStatus processingStatus);

@Query("""
Expand All @@ -35,12 +37,4 @@ public interface StudyRepository extends Repository<Study, Long> {
and s.processingStatus = :processingStatus
""")
List<Study> findByMemberAndProcessingStatus(@Param("member") Member member, @Param("processingStatus") ProcessingStatus processingStatus);

@Query("""
select distinct s from Study s
join StudyMember sm
on s = sm.study
where sm.member = :member
""")
List<Study> findStudiesOfMember(@Param("member") Member member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Positive;

public record StudyCreateRequest(
public record StudyUpdateRequest(
@NotBlank(message = "스터디 이름이 공백입니다.")
String name,
@Positive(message = "스터디 멤버 정원은 음수일 수 없습니다.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import com.yigongil.backend.domain.member.Member;
import com.yigongil.backend.request.ProfileUpdateRequest;
import com.yigongil.backend.response.MyProfileResponse;
import com.yigongil.backend.response.NicknameValidationResponse;
import com.yigongil.backend.response.ProfileResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/v1/members")
Expand Down Expand Up @@ -54,4 +56,9 @@ public ResponseEntity<Void> updateProfile(@Authorization Member member, @Request
return ResponseEntity.ok().build();
}

@GetMapping(path = "/exists")
public ResponseEntity<NicknameValidationResponse> existsByNickname(@RequestParam String nickname) {
NicknameValidationResponse response = memberService.existsByNickname(nickname);
return ResponseEntity.ok(response);
}
}
25 changes: 23 additions & 2 deletions backend/src/main/java/com/yigongil/backend/ui/StudyController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.yigongil.backend.application.StudyService;
import com.yigongil.backend.config.auth.Authorization;
import com.yigongil.backend.domain.member.Member;
import com.yigongil.backend.request.StudyCreateRequest;
import com.yigongil.backend.request.StudyUpdateRequest;
import com.yigongil.backend.response.MyStudyResponse;
import com.yigongil.backend.response.RecruitingStudyResponse;
import com.yigongil.backend.response.StudyDetailResponse;
Expand All @@ -17,8 +17,10 @@
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/v1/studies")
Expand All @@ -34,12 +36,22 @@ public StudyController(StudyService studyService) {
@PostMapping
public ResponseEntity<Void> createStudy(
@Authorization Member member,
@RequestBody @Valid StudyCreateRequest request
@RequestBody @Valid StudyUpdateRequest request
) {
Long studyId = studyService.create(member, request);
return ResponseEntity.created(URI.create("/v1/studies/" + studyId)).build();
}

@PutMapping("/{studyId}")
public ResponseEntity<Void> updateStudy(
@Authorization Member member,
@PathVariable Long studyId,
@RequestBody @Valid StudyUpdateRequest request
) {
studyService.update(member, studyId, request);
return ResponseEntity.ok().build();
}

@PostMapping("/{studyId}/applicants")
public ResponseEntity<Void> applyStudy(@Authorization Member member, @PathVariable Long studyId) {
studyService.apply(member, studyId);
Expand Down Expand Up @@ -74,6 +86,15 @@ public ResponseEntity<List<RecruitingStudyResponse>> findRecruitingStudies(int p
return ResponseEntity.ok(response);
}

@GetMapping("/recruiting/search")
public ResponseEntity<List<RecruitingStudyResponse>> findRecruitingStudiesWithSearch(
int page,
@RequestParam(name = "q") String word
) {
List<RecruitingStudyResponse> response = studyService.findRecruitingStudiesWithSearch(page, word);
return ResponseEntity.ok(response);
}

@GetMapping("/{id}/applicants")
public ResponseEntity<List<StudyMemberResponse>> findApplicantOfStudy(@PathVariable Long id, @Authorization Member master) {
List<StudyMemberResponse> applicants = studyService.findApplicantsOfStudy(id, master);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.yigongil.backend.ui;
package com.yigongil.backend.ui.exceptionhandler;

import com.yigongil.backend.exception.HttpException;
import io.jsonwebtoken.ExpiredJwtException;
Expand All @@ -13,6 +13,12 @@
@RestControllerAdvice
public class ApiExceptionHandler {

private final InternalServerErrorMessageConverter internalServerErrorMessageConverter;

public ApiExceptionHandler(InternalServerErrorMessageConverter internalServerErrorMessageConverter) {
this.internalServerErrorMessageConverter = internalServerErrorMessageConverter;
}

@ExceptionHandler
public ResponseEntity<String> handleHttpException(HttpException e) {
log.error("예외 발생: ", e);
Expand All @@ -38,6 +44,6 @@ public ResponseEntity<String> handleMethodArgumentNotValid(MethodArgumentNotVali
public ResponseEntity<String> handleException(Exception e) {
log.error("예상치 못한 예외 발생: ", e);
return ResponseEntity.internalServerError()
.body("서버 에러 발생");
.body(internalServerErrorMessageConverter.convert(e));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.yigongil.backend.ui.exceptionhandler;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Profile(value = "!prod")
@Component
public class DevInternalServerErrorMessageConverter implements InternalServerErrorMessageConverter {

@Override
public String convert(Exception e) {
return e.getClass().getSimpleName() + ": " + e.getLocalizedMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.yigongil.backend.ui.exceptionhandler;

public interface InternalServerErrorMessageConverter {

String convert(Exception e);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.yigongil.backend.ui.exceptionhandler;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Profile(value = "prod")
@Component
public class ProdInternalServerErrorMessageConverter implements InternalServerErrorMessageConverter {

@Override
public String convert(Exception e) {
return "서버 에러 발생";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yigongil.backend.config.oauth.JwtTokenProvider;
import com.yigongil.backend.request.ProfileUpdateRequest;
import com.yigongil.backend.response.NicknameValidationResponse;
import com.yigongil.backend.response.ProfileResponse;
import com.yigongil.backend.response.TokenResponse;
import io.cucumber.java.en.Given;
Expand All @@ -16,6 +17,7 @@
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

public class MemberSteps {
Expand Down Expand Up @@ -73,4 +75,18 @@ public MemberSteps(ObjectMapper objectMapper, SharedContext sharedContext, JwtTo
() -> assertThat(response.introduction()).isEqualTo(introduction)
);
}

@Then("{string}은 중복된 닉네임인 것을 확인할 수 있다.")
public void 중복_닉네임_확인(String nickname) {
ExtractableResponse<Response> response = given()
.when()
.get("/v1/members/exists?nickname=" + nickname)
.then().log().all()
.extract();

assertAll(
() -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()),
() -> assertThat(response.as(NicknameValidationResponse.class).exists()).isTrue()
);
}
}
Loading

0 comments on commit 798f096

Please sign in to comment.