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

키워드별 추천 포스트 어드민 기능을 구현한다 #1470

Merged
merged 25 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
583647b
feat: RecommendedPost 엔티티 추가
nuyh99 Jul 20, 2023
f0edbca
feat: 추천 포스트 등록, 수정, 삭제 기능 추가
nuyh99 Jul 20, 2023
4498cc5
test: 키워드별 추천 포스트 엔티티 테스트
nuyh99 Jul 20, 2023
3ed1604
feat: 어드민용 키워드별 추천 포스트 기능 컨트롤러 구현
nuyh99 Jul 20, 2023
6b5f624
fix: 추천 포스트 URL 길이 수정 및 FK 네임 변경
nuyh99 Jul 28, 2023
b268be4
feat: 키워드 조회 시 추천 포스트도 조회되도록 변경
nuyh99 Jul 28, 2023
fc6551a
test: 큐컴버 인수 테스트 추가
Jul 29, 2023
35f6479
test: 큐컴버 인수 테스트 추가
Jul 29, 2023
4d0d6e5
test: 큐컴버 인수 테스트 중복 조건 제거
Jul 29, 2023
25c3f7e
fix: RecommendedPostResponse dto 수정
Jul 30, 2023
6e85c29
fix: Keyword 저장 시 세션과의 연관 관계 violation 해결
nuyh99 Jul 31, 2023
7602e3d
fix: 예외 고도화 반영
nuyh99 Aug 2, 2023
07d9bfa
style: RecommendedPost 리포지토리 네이밍 변경
nuyh99 Aug 3, 2023
2c77205
refactor: RecommendedPost 삽입 응답값 수정
nuyh99 Aug 3, 2023
f3b0680
refactor: update 응답값 200 -> 204 수정
nuyh99 Aug 3, 2023
a8fd1e2
style: recommended post 관련 클래스 네이밍 수정
nuyh99 Aug 3, 2023
0c80f77
fix: test 내에서 Transactional로 롤백하던 것 DataInitializer로 대체
nuyh99 Aug 3, 2023
d5c7e2f
refactor: RecommendedPost 생성자 변경
nuyh99 Aug 3, 2023
b34e979
style: final 빠진 부분 수정
nuyh99 Aug 3, 2023
f0a9e62
feat: 도메인 검증 기능 추가 및 테스트
nuyh99 Aug 3, 2023
7124da0
feat: List로 가지던 연관 관계 Set으로 변경, Repository 메서드 네이밍 변경
nuyh99 Aug 10, 2023
0137acb
fix: findById 오버라이딩하도록 수정
nuyh99 Aug 10, 2023
10839e5
refactor: 테스트 코드 간결화 및 가독성 증진
nuyh99 Aug 10, 2023
c97e785
fix: RecommendedPost에서 url의 NPE 처리
nuyh99 Aug 10, 2023
e3c68a1
style: 개행 추가
nuyh99 Aug 10, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package wooteco.prolog.steps;

import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.springframework.http.HttpStatus;
import wooteco.prolog.AcceptanceSteps;
import wooteco.prolog.roadmap.application.dto.RecommendedRequest;

import static org.assertj.core.api.Assertions.assertThat;
import static wooteco.prolog.fixtures.KeywordAcceptanceFixture.KEYWORD_REQUEST;

public class KeywordRecommendedPostStepDefinitions extends AcceptanceSteps {

@Given("{int}번 키워드에 대해 추천 포스트 {string}를 작성하고")
@When("{int}번 키워드에 대해 추천 포스트 {string}를 작성하면")
public void 추천_포스트를_추가하면(int keywordId, String url) {
context.invokeHttpPost(
"/keywords/"+keywordId+"/recommended-posts",
new RecommendedRequest(url)
);
}

@When("{int}번 키워드에 대한 {int}번 추천 포스트를 {string}로 수정하면")
public void 추천_포스트를_수정하면(int keywordId, int recommendedId, String url) {
context.invokeHttpPut(
"/keywords/"+keywordId+"/recommended-posts/"+recommendedId,
new RecommendedRequest(url));
}

@When("{int}번 키워드에 대한 {int}번 추천 포스트를 삭제하면")
public void 추천_포스트를_삭제하면(int keywordId, int recommendedId) {
context.invokeHttpDelete(
"/keywords/" + keywordId + "/recommended-posts/" + recommendedId
);
}

@Then("추천 포스트가 생성된다")
public void 추천_포스트가_생성된다() {
int statusCode = context.response.statusCode();

assertThat(statusCode).isEqualTo(HttpStatus.CREATED.value());
}

@Then("추천 포스트가 수정된다")
public void 추천_포스트가_수정된다() {
int statusCode = context.response.statusCode();

assertThat(statusCode).isEqualTo(HttpStatus.OK.value());
}

@Then("추천 포스트가 삭제된다")
public void 추천_포스트가_삭제된다() {
int statusCode = context.response.statusCode();

assertThat(statusCode).isEqualTo(HttpStatus.NO_CONTENT.value());
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package wooteco.prolog.steps;

import static org.assertj.core.api.Assertions.assertThat;
import static wooteco.prolog.fixtures.KeywordAcceptanceFixture.KEYWORD_REQUEST;

import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.springframework.http.HttpStatus;
import wooteco.prolog.AcceptanceSteps;
import wooteco.prolog.session.application.dto.SessionRequest;

import static org.assertj.core.api.Assertions.assertThat;
import static wooteco.prolog.fixtures.KeywordAcceptanceFixture.KEYWORD_REQUEST;

public class KeywordStepDefinitions extends AcceptanceSteps {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@api
Feature: 로드맵 키워드 추천 포스트 관련 기능

Background: 사전 작업
Given "2022 백엔드 레벨1" 세션을 생성하고 - 1번 세션
And 1번 세션에 "자바"라는 키워드를 순서 1, 중요도 2로 작성하고

Scenario: 키워드 추천 포스트 생성하기
When 1번 키워드에 대해 추천 포스트 "https://javajavajava"를 작성하면
Then 추천 포스트가 생성된다

Scenario: 키워드 추천 포스트 수정하기
Given 1번 키워드에 대해 추천 포스트 "https://javajavajava"를 작성하고
When 1번 키워드에 대한 1번 추천 포스트를 "https://java2java2"로 수정하면
Then 추천 포스트가 수정된다

Scenario: 키워드 추천 포스트 삭제하기
Given 1번 키워드에 대해 추천 포스트 "https://javajavajava"를 작성하고
When 1번 키워드에 대한 1번 추천 포스트를 삭제하면
Then 추천 포스트가 삭제된다
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
package wooteco.prolog.docu;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doNothing;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;

import java.util.Arrays;
import java.util.HashSet;
import org.elasticsearch.common.collect.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
Expand All @@ -21,6 +14,14 @@
import wooteco.prolog.roadmap.application.dto.KeywordsResponse;
import wooteco.prolog.roadmap.ui.KeywordController;

import java.util.Arrays;
import java.util.HashSet;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doNothing;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;

@WebMvcTest(controllers = KeywordController.class)
public class KeywordDocumentation extends NewDocumentation {

Expand Down Expand Up @@ -108,6 +109,7 @@ public class KeywordDocumentation extends NewDocumentation {
1,
1,
null,
null,
null
);

Expand All @@ -133,6 +135,7 @@ public class KeywordDocumentation extends NewDocumentation {
1,
1,
null,
null,
new HashSet<>(
Arrays.asList(
new KeywordResponse(
Expand All @@ -142,6 +145,7 @@ public class KeywordDocumentation extends NewDocumentation {
1,
1,
1L,
null,
null
),
new KeywordResponse(
Expand All @@ -151,6 +155,7 @@ public class KeywordDocumentation extends NewDocumentation {
2,
1,
1L,
null,
null
))
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.AllArgsConstructor;
import lombok.Getter;
import wooteco.prolog.roadmap.domain.RecommendedPost;
import wooteco.prolog.session.domain.Mission;
import wooteco.prolog.session.domain.Session;
import wooteco.prolog.studylog.domain.TagName;
Expand Down Expand Up @@ -70,6 +71,10 @@ public enum BadRequestCode {
NOT_EMPTY_ESSAY_ANSWER_EXCEPTION(8013, "답변은 공백일 수 없습니다."),
ESSAY_ANSWER_NOT_VALID_USER(8014, "본인이 작성한 답변만 수정할 수 있습니다."),

ROADMAP_RECOMMENDED_POST_NOT_FOUND(8101, "해당 추천 포스트가 존재하지 않습니다."),
ROADMAP_RECOMMENDED_POST_INVALID_URL_LENGTH(8102, String.format(
"해당 추천 포스트의 URL 길이는 1 ~ %d여야 합니다.", RecommendedPost.URL_LENGTH_UPPER_BOUND)),

FILE_NAME_EMPTY_EXCEPTION(9001, "파일 이름이 존재하지 않습니다."),
UNSUPPORTED_FILE_EXTENSION_EXCEPTION(9002, "지원하지 않는 파일 확장자입니다."),
FILE_UPLOAD_FAIL_EXCEPTION(9003, "파일 업로드에 실패했습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package wooteco.prolog.roadmap.application;

import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_KEYWORD_NOT_FOUND_EXCEPTION;
import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_SESSION_NOT_FOUND_EXCEPTION;

import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import wooteco.prolog.common.exception.BadRequestException;
Expand All @@ -15,6 +11,11 @@
import wooteco.prolog.roadmap.domain.repository.KeywordRepository;
import wooteco.prolog.session.domain.repository.SessionRepository;

import java.util.List;

import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_KEYWORD_NOT_FOUND_EXCEPTION;
import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_SESSION_NOT_FOUND_EXCEPTION;

@Transactional
@Service
public class KeywordService {
Expand Down Expand Up @@ -55,7 +56,7 @@ public KeywordResponse findKeywordWithAllChild(final Long sessionId, final Long
existSession(sessionId);
existKeyword(keywordId);

Keyword keyword = keywordRepository.findFetchById(keywordId);
Keyword keyword = keywordRepository.findFetchByIdOrderBySeq(keywordId);

return KeywordResponse.createWithAllChildResponse(keyword);
}
Expand All @@ -82,7 +83,7 @@ public void updateKeyword(final Long sessionId, final Long keywordId,

public void deleteKeyword(final Long sessionId, final Long keywordId) {
existSession(sessionId);
Keyword keyword = keywordRepository.findFetchById(keywordId);
Keyword keyword = keywordRepository.findFetchByIdOrderBySeq(keywordId);

keywordRepository.delete(keyword);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package wooteco.prolog.roadmap.application;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import wooteco.prolog.common.exception.BadRequestException;
import wooteco.prolog.roadmap.application.dto.RecommendedRequest;
import wooteco.prolog.roadmap.application.dto.RecommendedUpdateRequest;
import wooteco.prolog.roadmap.domain.Keyword;
import wooteco.prolog.roadmap.domain.RecommendedPost;
import wooteco.prolog.roadmap.domain.repository.KeywordRepository;
import wooteco.prolog.roadmap.domain.repository.RecommendedPostRepository;

import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_KEYWORD_NOT_FOUND_EXCEPTION;
import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_RECOMMENDED_POST_NOT_FOUND;

@Transactional(readOnly = true)
@Service
public class RecommendedPostService {

private final RecommendedPostRepository recommendedPostRepository;
private final KeywordRepository keywordRepository;

public RecommendedPostService(final RecommendedPostRepository recommendedPostRepository,
final KeywordRepository keywordRepository) {
this.recommendedPostRepository = recommendedPostRepository;
this.keywordRepository = keywordRepository;
}

@Transactional
public Long create(final Long keywordId, final RecommendedRequest request) {
final Keyword keyword = findKeywordOrThrow(keywordId);
final RecommendedPost post = new RecommendedPost(request.getUrl(), keyword);

return recommendedPostRepository.save(post).getId();
}

private Keyword findKeywordOrThrow(final Long keywordId) {
return keywordRepository.findById(keywordId)
.orElseThrow(() -> new BadRequestException(ROADMAP_KEYWORD_NOT_FOUND_EXCEPTION));
}

@Transactional
public void update(final Long recommendedId, final RecommendedUpdateRequest request) {
final RecommendedPost post = findPostOrThrow(recommendedId);

post.updateUrl(request.getUrl());
}

private RecommendedPost findPostOrThrow(final Long recommendedId) {
return recommendedPostRepository.findById(recommendedId)
.orElseThrow(() -> new BadRequestException(ROADMAP_RECOMMENDED_POST_NOT_FOUND));
}

@Transactional
public void delete(final Long recommendedId) {
final RecommendedPost recommendedPost = findPostOrThrow(recommendedId);
recommendedPost.remove();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package wooteco.prolog.roadmap.application.dto;

import java.util.HashSet;
import java.util.Set;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import wooteco.prolog.roadmap.domain.Keyword;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class KeywordResponse {
Expand All @@ -17,18 +20,21 @@ public class KeywordResponse {
private int order;
private int importance;
private Long parentKeywordId;
private List<RecommendedPostResponse> recommendedPosts;
private Set<KeywordResponse> childrenKeywords;

public KeywordResponse(final Long keywordId, final String name, final String description,
final int order,
final int importance, final Long parentKeywordId,
final List<RecommendedPostResponse> recommendedPosts,
final Set<KeywordResponse> childrenKeywords) {
this.keywordId = keywordId;
this.name = name;
this.description = description;
this.order = order;
this.importance = importance;
this.parentKeywordId = parentKeywordId;
this.recommendedPosts = recommendedPosts;
this.childrenKeywords = childrenKeywords;
}

Expand All @@ -40,9 +46,16 @@ public static KeywordResponse createResponse(final Keyword keyword) {
keyword.getSeq(),
keyword.getImportance(),
keyword.getParentIdOrNull(),
createRecommendedPostResponses(keyword),
null);
}

private static List<RecommendedPostResponse> createRecommendedPostResponses(final Keyword keyword) {
return keyword.getRecommendedPosts().stream()
.map(RecommendedPostResponse::from)
.collect(Collectors.toList());
}

public static KeywordResponse createWithAllChildResponse(final Keyword keyword) {
return new KeywordResponse(
keyword.getId(),
Expand All @@ -51,6 +64,7 @@ public static KeywordResponse createWithAllChildResponse(final Keyword keyword)
keyword.getSeq(),
keyword.getImportance(),
keyword.getParentIdOrNull(),
createRecommendedPostResponses(keyword),
createKeywordChild(keyword.getChildren()));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package wooteco.prolog.roadmap.application.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import wooteco.prolog.roadmap.domain.RecommendedPost;

@AllArgsConstructor
@Getter
public class RecommendedPostResponse {

private final Long id;
private final String url;

public static RecommendedPostResponse from(final RecommendedPost recommendedPost) {
return new RecommendedPostResponse(recommendedPost.getId(), recommendedPost.getUrl());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package wooteco.prolog.roadmap.application.dto;

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

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class RecommendedRequest {

private String url;
Copy link
Contributor

Choose a reason for hiding this comment

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

여기도 마찬가지로 @NotEmpty 같은 제약 조건이 필요할 것 같습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

도메인에 검증 메서드 추가했습니다!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package wooteco.prolog.roadmap.application.dto;

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

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class RecommendedUpdateRequest {

private String url;
Copy link
Contributor

Choose a reason for hiding this comment

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

@NotEmpty 같은 제약 조건이나 혹은 Url에 대한 패턴 매칭이 필요할 것 같아요!
현재는 null이 들어와도 그대로 db에 null이 들어갈 수 있어서...!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

도메인에 검증 메서드 추가했습니다!

}
Loading