Skip to content

Commit

Permalink
Merge pull request #1470 from woowacourse/feat/1397-recommended-posts
Browse files Browse the repository at this point in the history
키워드별 추천 포스트 어드민 기능을 구현한다
  • Loading branch information
nuyh99 authored Aug 16, 2023
2 parents 703ffb7 + e3c68a1 commit e3ff353
Show file tree
Hide file tree
Showing 21 changed files with 619 additions and 69 deletions.
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;
}
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;
}
Loading

0 comments on commit e3ff353

Please sign in to comment.