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

feat: 셀프 소개 기능을 구현한다 #46

Merged
merged 28 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4765be2
feat: 셀프 소개글 기능에서 발생할 수 있는 예외클래스 지정
eom-tae-in Jul 16, 2024
758223f
feat: 셀프 소개글 기능에서 사용할 request dto 생성
eom-tae-in Jul 16, 2024
312ff9b
feat: 셀프 소개글 기능에서 사용할 response dto 생성
eom-tae-in Jul 16, 2024
c5ccac2
feat: 셀프 소개글을 저장하고 관리하는 엔티티 구현
eom-tae-in Jul 16, 2024
566d9c4
feat: 셀프 소개글을 저장, 조회, 삭제하기 위한 repository 구현
eom-tae-in Jul 16, 2024
05db37a
feat: 셀프 소개글의 읽기 전용 Service 구현
eom-tae-in Jul 16, 2024
4e7ed5b
feat: 셀프 소개글의 쓰기 전용 Service 구현
eom-tae-in Jul 16, 2024
457cc98
feat: 셀프 소개글 Controller 구현
eom-tae-in Jul 16, 2024
a073dc7
feat: 셀프 소개글 기능 이용시 인증을 위한 인터셉터에 url에 추가
eom-tae-in Jul 16, 2024
52d2f89
test: 테스트 패키지 구조 변경으로 인한 수정
eom-tae-in Jul 16, 2024
9f003c1
test: SelfIntro를 검증하기 위한 테스트 클래스 작성
eom-tae-in Jul 16, 2024
1a95f1e
test: SelfIntroService를 검증하기 위한 테스트 클래스 작성
eom-tae-in Jul 16, 2024
87d3f19
test: SelfIntroQueryService를 검증하기 위한 테스트 클래스 작성
eom-tae-in Jul 16, 2024
23b7fee
test: SelfIntroQueryRepository를 검증하기 위한 테스트 클래스 작성
eom-tae-in Jul 16, 2024
dd63c4f
test: SelfIntroJpaRepository를 검증하기 위한 테스트 클래스 작성
eom-tae-in Jul 16, 2024
f2eb231
test: SelfIntro 컨트롤러 단위테스트 작성
eom-tae-in Jul 16, 2024
c88c9b4
test: SelfIntro 인수테스트 작성
eom-tae-in Jul 16, 2024
b5e3fbd
test: SelfIntro 인수테스트 설정을 하기 위해 helper 작성
eom-tae-in Jul 16, 2024
39c23a0
test: SelfIntro 서비스 계층 테스트시 사용할 FakeRepository 작성
eom-tae-in Jul 16, 2024
99aa725
test: 테스트를 편하게 하기 위해 fixture 작
eom-tae-in Jul 16, 2024
6133fc3
test: 경로 변경으로 인한 테스트 코드 수정
eom-tae-in Jul 16, 2024
3baa3fd
refactor: 개행 추가
eom-tae-in Jul 16, 2024
7eef749
refactor: 메서드 구조 변경
eom-tae-in Jul 16, 2024
8b81c89
test: test 수정
eom-tae-in Jul 16, 2024
a16ae8a
refactor: 상수 값 변경
eom-tae-in Jul 16, 2024
c7728f4
refactor: 줄바꿈 제거
eom-tae-in Jul 16, 2024
9192c4e
refactor: 사용하는 jpa 내장 메서드 가시화
eom-tae-in Jul 16, 2024
fe42b49
docs: selfintro api 명세서 작성
eom-tae-in Jul 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion src/docs/asciidoc/member.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
:sectlinks:
:sectnums:

== Member
= Member


== Auth


=== 로그인 (POST api/members/auth/login)

Expand All @@ -18,6 +22,9 @@ include::{snippets}/member-auth-controller-web-mvc-test/유저_로그인/http-re
include::{snippets}/member-auth-controller-web-mvc-test/유저_로그인/request-fields.adoc[]


== Member


=== 닉네임 중복 확인 (GET /api/members/nickname/existence)

==== 요청
Expand All @@ -41,6 +48,7 @@ include::{snippets}/member-controller-web-mvc-test/회원_정보_초기화/http-


=== 회원 정보 조회(GET /api/members)

==== 요청
include::{snippets}/member-controller-web-mvc-test/회원_정보_조회/request-headers.adoc[]
include::{snippets}/member-controller-web-mvc-test/회원_정보_조회/http-request.adoc[]
Expand Down Expand Up @@ -69,3 +77,64 @@ include::{snippets}/member-controller-web-mvc-test/회원_삭제/http-request.ad

==== 응답
include::{snippets}/member-controller-web-mvc-test/회원_삭제/http-response.adoc[]


== SelfIntro


=== 셀프 소개글 저장 (POST /api/members/self-intros/me)

==== 요청
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_저장/http-request.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_저장/request-headers.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_저장/request-fields.adoc[]

==== 응답
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_저장/http-response.adoc[]


=== 셀프 소개글 페이징 조회 (GET /api/members/self-intros)

==== 요청
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_페이징_조회/http-request.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_페이징_조회/request-headers.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_페이징_조회/request-parts.adoc[]

==== 응답
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_페이징_조회/http-response.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_페이징_조회/response-fields.adoc[]


=== 셀프 소개글 페이징 조회 (필터 적용) (Get /api/members/self-intros/filter)

==== 요청
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_페이징_조회(필터_적용)/http-request.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_페이징_조회(필터_적용)/request-headers.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_페이징_조회(필터_적용)/request-parts.adoc[]

==== 응답
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_페이징_조회(필터_적용)/http-response.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_페이징_조회(필터_적용)/response-fields.adoc[]


=== 셀프 소개글 변경 (Patch /api/members/self-intros/{id})

==== 요청
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_변경/http-request.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_변경/request-headers.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_변경/path-parameters.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_변경/request-fields.adoc[]

==== 응답
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_변경/http-response.adoc[]


=== 셀프 소개글 삭제 (Delete /api/members/self-intros/{id})

==== 요청
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_삭제/http-request.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_삭제/request-headers.adoc[]
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_삭제/path-parameters.adoc[]

==== 응답
include::{snippets}/self-intro-controller-web-mvc-test/셀프_소개글_삭제/http-response.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.atwoz.member.application.selfintro;

import com.atwoz.member.application.selfintro.dto.SelfIntroFilterRequest;
import com.atwoz.member.application.selfintro.dto.SelfIntrosResponse;
import com.atwoz.member.domain.selfintro.SelfIntroRepository;
import com.atwoz.member.infrastructure.selfintro.dto.SelfIntroResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class SelfIntroQueryService {

private final SelfIntroRepository selfIntroRepository;

public SelfIntrosResponse findAllSelfIntrosWithPaging(final Pageable pageable,
final Long memberId) {
Page<SelfIntroResponse> selfIntroResponses = selfIntroRepository.findAllSelfIntrosWithPaging(pageable, memberId);

return SelfIntrosResponse.of(selfIntroResponses, pageable);
}

public SelfIntrosResponse findAllSelfIntrosWithPagingAndFiltering(final Pageable pageable,
final SelfIntroFilterRequest selfIntroFilterRequest,
final Long memberId) {
Page<SelfIntroResponse> selfIntroResponses = selfIntroRepository.findAllSelfIntrosWithPagingAndFiltering(
pageable,
selfIntroFilterRequest.minAge(),
selfIntroFilterRequest.maxAge(),
selfIntroFilterRequest.isOnlyOppositeGender(),
selfIntroFilterRequest.getCities(),
memberId
);

return SelfIntrosResponse.of(selfIntroResponses, pageable);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.atwoz.member.application.selfintro;

import com.atwoz.member.application.selfintro.dto.SelfIntroCreateRequest;
import com.atwoz.member.application.selfintro.dto.SelfIntroUpdateRequest;
import com.atwoz.member.domain.selfintro.SelfIntro;
import com.atwoz.member.domain.selfintro.SelfIntroRepository;
import com.atwoz.member.exception.exceptions.selfintro.SelfIntroNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
@Transactional
public class SelfIntroService {

private final SelfIntroRepository selfIntroRepository;

public void saveSelfIntro(final SelfIntroCreateRequest selfIntroCreateRequest,
final Long memberId) {
SelfIntro selfIntro = SelfIntro.createWith(memberId, selfIntroCreateRequest.content());
selfIntroRepository.save(selfIntro);
}

public void updateSelfIntro(final SelfIntroUpdateRequest selfIntroUpdateRequest,
final Long selfIntroId,
final Long memberId) {
SelfIntro foundSelfIntro = findSelfIntroById(selfIntroId);
foundSelfIntro.validateSameWriter(memberId);
foundSelfIntro.update(selfIntroUpdateRequest.content());
}

public void deleteSelfIntro(final Long selfIntroId, final Long memberId) {
SelfIntro foundSelfIntro = findSelfIntroById(selfIntroId);
foundSelfIntro.validateSameWriter(memberId);
selfIntroRepository.deleteById(foundSelfIntro.getId());
}

private SelfIntro findSelfIntroById(final Long selfIntroId) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

updateSelfIntro와 deleteSelfIntro에서 모두 findSelfIntroById를 거친 뒤 validateSameWriter를 호출하고 있는데, 이러한 경우라면 findSelfIntroById 안에 validateSameWriter까지 넣어보는 것은 어떨까요? SelfIntro 조회 시 validateSameWriter를 호출해야 한다는 조건이 들어가있다면 findSelfIntroById에서 넣어둬도 괜찮지 않을까라는 생각이 드는데 의견이 궁금합니다 :)

return selfIntroRepository.findById(selfIntroId)
.orElseThrow(SelfIntroNotFoundException::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.atwoz.member.application.selfintro.dto;

import jakarta.validation.constraints.NotBlank;

public record CityRequest(

@NotBlank(message = "선호하는 지역 정보를 입력해주세요.")
String city
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.atwoz.member.application.selfintro.dto;

import jakarta.validation.constraints.NotBlank;

public record SelfIntroCreateRequest(

@NotBlank(message = "소개글을 입력해주세요")
String content
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.atwoz.member.application.selfintro.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.List;

public record SelfIntroFilterRequest(

@NotNull(message = "최소 나이를 입력해주세요")
Integer minAge,

@NotNull(message = "최대 나이를 입력해주세요")
Integer maxAge,

@NotNull(message = "성별을 선택해주세요")
Boolean isOnlyOppositeGender,

@Valid
@NotEmpty(message = "선호 지역을 하나 이상 입력해주세요.")
List<CityRequest> cityRequests
) {

public List<String> getCities() {
return cityRequests.stream()
.map(CityRequest::city)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.atwoz.member.application.selfintro.dto;

import jakarta.validation.constraints.NotBlank;

public record SelfIntroUpdateRequest(

@NotBlank(message = "소개글을 입력해주세요")
String content
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.atwoz.member.application.selfintro.dto;

import com.atwoz.member.infrastructure.selfintro.dto.SelfIntroResponse;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public record SelfIntrosResponse(
List<SelfIntroResponse> selfIntros,
int nowPage,
int nextPage,
int totalPages
) {

private static final int NEXT_PAGE_INDEX = 1;
private static final int NO_MORE_PAGE = -1;

public static SelfIntrosResponse of(final Page<SelfIntroResponse> selfIntros,
final Pageable pageable) {
return new SelfIntrosResponse(
selfIntros.getContent(),
pageable.getPageNumber(),
getNextPage(pageable.getPageNumber(), selfIntros),
selfIntros.getTotalPages()
);
}

private static int getNextPage(final int pageNumber, final Page<SelfIntroResponse> selfIntros) {
if (selfIntros.hasNext()) {
return pageNumber + NEXT_PAGE_INDEX;
}

return NO_MORE_PAGE;
}
}
4 changes: 3 additions & 1 deletion src/main/java/com/atwoz/member/config/MemberAuthConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ private HandlerInterceptor loginValidCheckerInterceptor() {
.excludePathPattern("/**", OPTIONS)
.excludePathPattern("/api/missions/**", GET, POST, PATCH, DELETE)
.excludePathPattern("/api/surveys/**", GET, POST)
.excludePathPattern("/api/members/auth/**", POST)
.addPathPatterns("/api/members/**", GET, POST, PATCH, DELETE)
.addPathPatterns("/api/reports/**", POST)
.addPathPatterns("/api/surveys/**", GET, POST)
.addPathPatterns("/api/members/me/missions/**", GET, POST, PATCH)
.addPathPatterns("/api/members/me/surveys/**", GET, POST);
.addPathPatterns("/api/members/me/surveys/**", GET, POST)
.addPathPatterns("/api/members/self-intros/**", GET, POST, PATCH, DELETE);
}

@Override
Expand Down
66 changes: 66 additions & 0 deletions src/main/java/com/atwoz/member/domain/selfintro/SelfIntro.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.atwoz.member.domain.selfintro;

import com.atwoz.global.domain.BaseEntity;
import com.atwoz.member.exception.exceptions.selfintro.InvalidContentException;
import com.atwoz.member.exception.exceptions.selfintro.WriterNotEqualsException;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.util.Objects;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@EqualsAndHashCode(of = "id", callSuper = false)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Entity
public class SelfIntro extends BaseEntity {

private static final int MIN_LENGTH = 1;
private static final int MAX_LENGTH = 30;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private Long memberId;

@Column(length = MAX_LENGTH, nullable = false)
private String content;

public static SelfIntro createWith(final Long memberId,
final String content) {
validateContent(content);
return SelfIntro.builder()
.memberId(memberId)
.content(content)
.build();
}

private static void validateContent(final String content) {
int contentLength = content.length();
if (contentLength < MIN_LENGTH || MAX_LENGTH < contentLength) {
throw new InvalidContentException();
}
}

public void validateSameWriter(final Long memberId) {
if (!Objects.equals(this.memberId, memberId)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

저였다면 습관적으로 직접 두 필드를 비교하는 방식을 썼을 것 같은데 Objects에 있는 걸 이용하면 더 간단히 작성할 수 있어서 좋은 것 같습니다!

throw new WriterNotEqualsException();
}
}

public void update(final String content) {
validateContent(content);
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.atwoz.member.domain.selfintro;

import com.atwoz.member.infrastructure.selfintro.dto.SelfIntroResponse;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface SelfIntroRepository {

SelfIntro save(SelfIntro selfIntro);

Optional<SelfIntro> findById(Long id);

void deleteById(Long id);

Page<SelfIntroResponse> findAllSelfIntrosWithPaging(Pageable pageable, Long memberId);

Page<SelfIntroResponse> findAllSelfIntrosWithPagingAndFiltering(Pageable pageable, int minAge, int maxAge,
boolean isOnlyOppositeGender, List<String> cities,
Long memberId);
}
Loading
Loading