From 43253c5657cb956571baa98b2642b42911d66d32 Mon Sep 17 00:00:00 2001 From: YoungHo Choi <0505zxc@gmail.com> Date: Tue, 16 Aug 2022 03:01:23 +0900 Subject: [PATCH 01/16] =?UTF-8?q?feat:=20AuthenticationManager=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authentication/provider/AuthenticationManager.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/nextstep/auth/authentication/provider/AuthenticationManager.java diff --git a/src/main/java/nextstep/auth/authentication/provider/AuthenticationManager.java b/src/main/java/nextstep/auth/authentication/provider/AuthenticationManager.java new file mode 100644 index 000000000..a33f2a432 --- /dev/null +++ b/src/main/java/nextstep/auth/authentication/provider/AuthenticationManager.java @@ -0,0 +1,8 @@ +package nextstep.auth.authentication.provider; + +import nextstep.auth.authentication.AuthenticationException; +import nextstep.auth.context.Authentication; + +public interface AuthenticationManager { + Authentication authenticate(Authentication authentication) throws AuthenticationException; +} From a7c0499cd96b5ae00b685b40954882e999493efc Mon Sep 17 00:00:00 2001 From: epicarts <0505zxc@gmail.com> Date: Tue, 16 Aug 2022 18:14:39 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat:=20Spring=20Security=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A1=9C=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/authentication/ProviderManager.java | 54 +++++++++++++++++++ .../provider/AuthenticationManager.java | 8 --- 2 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 src/main/java/nextstep/auth/authentication/ProviderManager.java delete mode 100644 src/main/java/nextstep/auth/authentication/provider/AuthenticationManager.java diff --git a/src/main/java/nextstep/auth/authentication/ProviderManager.java b/src/main/java/nextstep/auth/authentication/ProviderManager.java new file mode 100644 index 000000000..73c043457 --- /dev/null +++ b/src/main/java/nextstep/auth/authentication/ProviderManager.java @@ -0,0 +1,54 @@ +package nextstep.auth.authentication; + +import io.jsonwebtoken.lang.Assert; +import nextstep.auth.authentication.execption.AuthenticationException; +import nextstep.auth.authentication.provider.AuthenticationProvider; +import nextstep.auth.context.Authentication; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +// ProividerManager는 for문을 통해 모든 provider를 조회하면서 authenticate 인증 처리를 한다\ +public class ProviderManager implements AuthenticationManager { + private List providers = Collections.emptyList(); + private AuthenticationManager parent; + + + public ProviderManager(List providers) { + Assert.notNull(providers, "providers list cannot be null"); + this.providers = providers; + } + + public ProviderManager(List providers, AuthenticationManager parent) { + Assert.notNull(providers, "providers list cannot be null"); + this.providers = providers; + this.parent = parent; + } + + // 모든 provider 를 조회하면서 인증 처리를 함. + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Authentication result = null; + Authentication parentResult = null; + + for (AuthenticationProvider provider : getProviders()) { + result = provider.authenticate(authentication); + if (result != null) { + break; + } + } + + // ProviderManager 에게 처리할수 있는 Provider 가 존재하지 않을경우 Parent 에게 위임함. + if (result == null) { + parentResult = this.parent.authenticate(authentication); + result = parentResult; + } + + return result; + } + + public List getProviders() { + return this.providers; + } +} diff --git a/src/main/java/nextstep/auth/authentication/provider/AuthenticationManager.java b/src/main/java/nextstep/auth/authentication/provider/AuthenticationManager.java deleted file mode 100644 index a33f2a432..000000000 --- a/src/main/java/nextstep/auth/authentication/provider/AuthenticationManager.java +++ /dev/null @@ -1,8 +0,0 @@ -package nextstep.auth.authentication.provider; - -import nextstep.auth.authentication.AuthenticationException; -import nextstep.auth.context.Authentication; - -public interface AuthenticationManager { - Authentication authenticate(Authentication authentication) throws AuthenticationException; -} From 2528f5695b575286ce9eea78c992b91599ae3917 Mon Sep 17 00:00:00 2001 From: YoungHo Choi <0505zxc@gmail.com> Date: Wed, 17 Aug 2022 02:10:10 +0900 Subject: [PATCH 03/16] =?UTF-8?q?feat:=20AbstractAuthenticationFilter?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EC=83=81=ED=99=94=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/authentication/ProviderManager.java | 54 ------------------- 1 file changed, 54 deletions(-) delete mode 100644 src/main/java/nextstep/auth/authentication/ProviderManager.java diff --git a/src/main/java/nextstep/auth/authentication/ProviderManager.java b/src/main/java/nextstep/auth/authentication/ProviderManager.java deleted file mode 100644 index 73c043457..000000000 --- a/src/main/java/nextstep/auth/authentication/ProviderManager.java +++ /dev/null @@ -1,54 +0,0 @@ -package nextstep.auth.authentication; - -import io.jsonwebtoken.lang.Assert; -import nextstep.auth.authentication.execption.AuthenticationException; -import nextstep.auth.authentication.provider.AuthenticationProvider; -import nextstep.auth.context.Authentication; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -// ProividerManager는 for문을 통해 모든 provider를 조회하면서 authenticate 인증 처리를 한다\ -public class ProviderManager implements AuthenticationManager { - private List providers = Collections.emptyList(); - private AuthenticationManager parent; - - - public ProviderManager(List providers) { - Assert.notNull(providers, "providers list cannot be null"); - this.providers = providers; - } - - public ProviderManager(List providers, AuthenticationManager parent) { - Assert.notNull(providers, "providers list cannot be null"); - this.providers = providers; - this.parent = parent; - } - - // 모든 provider 를 조회하면서 인증 처리를 함. - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - Authentication result = null; - Authentication parentResult = null; - - for (AuthenticationProvider provider : getProviders()) { - result = provider.authenticate(authentication); - if (result != null) { - break; - } - } - - // ProviderManager 에게 처리할수 있는 Provider 가 존재하지 않을경우 Parent 에게 위임함. - if (result == null) { - parentResult = this.parent.authenticate(authentication); - result = parentResult; - } - - return result; - } - - public List getProviders() { - return this.providers; - } -} From 44420adaedc183e15c645a76c72b6a861b5b1379 Mon Sep 17 00:00:00 2001 From: epicarts <0505zxc@gmail.com> Date: Wed, 17 Aug 2022 14:08:54 +0900 Subject: [PATCH 04/16] =?UTF-8?q?test:=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EC=9D=B8=EC=88=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceptance/FavoriteAcceptanceTest.java | 181 ++++++++++++++++++ .../subway/acceptance/FavoriteSteps.java | 46 +++++ 2 files changed, 227 insertions(+) create mode 100644 src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java create mode 100644 src/test/java/nextstep/subway/acceptance/FavoriteSteps.java diff --git a/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java new file mode 100644 index 000000000..d9b5cdd9f --- /dev/null +++ b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java @@ -0,0 +1,181 @@ +package nextstep.subway.acceptance; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; + +import java.util.HashMap; +import java.util.Map; + +import static nextstep.DataLoader.*; +import static nextstep.subway.acceptance.FavoriteSteps.*; +import static nextstep.subway.acceptance.MemberSteps.로그인_되어_있음; +import static nextstep.subway.acceptance.StationSteps.지하철역_생성_요청; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("지하철 즐겨찾기 기능") +public class FavoriteAcceptanceTest extends AcceptanceTest { + private String 일반_사용자; + private String 관리자; + private Long 교대역; + private Long 강남역; + private Long 양재역; + private Long 남부터미널역; + private Long 이호선; + private Long 신분당선; + private Long 삼호선; + + /** + * 교대역 --- *2호선* --- 강남역 + * | | + * *3호선* *신분당선* + * | | + * 남부터미널역 --- *3호선* --- 양재 + */ + @BeforeEach + public void setUp() { + super.setUp(); + 일반_사용자 = 로그인_되어_있음(MEMBER_EMAIL, MEMBER_PASSWORD); + 관리자 = 로그인_되어_있음(ADMIN_EMAIL, ADMIN_PASSWORD); + + 교대역 = 지하철역_생성_요청(관리자, "교대역").jsonPath().getLong("id"); + 강남역 = 지하철역_생성_요청(관리자, "강남역").jsonPath().getLong("id"); + 양재역 = 지하철역_생성_요청(관리자, "양재역").jsonPath().getLong("id"); + 남부터미널역 = 지하철역_생성_요청(관리자, "남부터미널역").jsonPath().getLong("id"); + + 이호선 = 지하철_노선_생성_요청("2호선", "green", 교대역, 강남역, 10); + 신분당선 = 지하철_노선_생성_요청("신분당선", "red", 강남역, 양재역, 10); + 삼호선 = 지하철_노선_생성_요청("3호선", "orange", 교대역, 남부터미널역, 2); + } + + /** + * When 지하철 즐겨찾기 노선을 생성하면, + * Then 즐겨찾기 노선이 생성된다. + */ + @DisplayName("지하철 즐겨찾기 노선 생성") + @Test + void 즐겨찾기_노선을_생성한다() { + // when + ExtractableResponse response = 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + } + + /** + * When 지하철 즐겨찾기 노선을 생성하고, + * Then 즐겨찾기 노선을 조회하면, + * Then 생성한 즐겨찾기 노선이 조회된다. + */ + @DisplayName("지하철 즐겨찾기 노선 조회") + @Test + void 즐겨찾기_노선이_조회된다() { + // when + 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역); + + // then + ExtractableResponse response = 지하철_노선_즐겨찾기_조회_요청(일반_사용자); + + // then + assertAll( + () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()), + () -> Assertions.assertThat(response.jsonPath().getList("source.name", String.class)).containsExactly("교대역"), + () -> Assertions.assertThat(response.jsonPath().getList("target.name", String.class)).containsExactly("양재역") + ); + } + + /** + * When 지하철 즐겨찾기 노선을 2번 생성하고, + * Then 즐겨찾기 노선을 조회하면, + * Then 2번 생성한 즐겨찾기 노선이 전부 조회된다. + */ + @DisplayName("여러 지하철 즐겨찾기 노선 조회") + @Test + void 즐겨찾기_노선_모두가_조회된다() { + // when + 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역); + 지하철_노선_즐겨찾기_생성(일반_사용자, 남부터미널역, 양재역); + + // then + ExtractableResponse response = 지하철_노선_즐겨찾기_조회_요청(일반_사용자); + + // then + assertAll( + () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()), + () -> Assertions.assertThat(response.jsonPath().getList("source.name", String.class)).containsExactly("교대역", "남부터미널역"), + () -> Assertions.assertThat(response.jsonPath().getList("target.name", String.class)).containsExactly("양재역", "양재역") + ); + } + + /** + * When 지하철 즐겨찾기 노선을 생성하고, + * Then 즐겨찾기 노선을 삭제하면, + * Then 즐겨찾기가 삭제된다. + */ + @DisplayName("즐겨찾기 노선 삭제") + @Test + void 즐겨찾기_노선이_삭제된다() { + // when + String 즐겨찾기_노선 = 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역).header("location"); + + // then + ExtractableResponse response = 지하철_노선_즐겨찾기_제거_요청(일반_사용자, 즐겨찾기_노선); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + + /** + * Given 지하철 즐겨찾기 노선을 생성하고, + * When 유효하지 않은 토큰이 즐겨찾기를 삭제 요청하면, + * Then 권한이 없어 (401)을 응답한다. + */ + @DisplayName("유효하지 않은 로그인 정보로 삭제할 수 없다") + @Test + void 유효하지_않은_토큰으로요청() { + // given + String 즐겨찾기_노선 = 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역).header("location"); + + // when + ExtractableResponse response = 지하철_노선_즐겨찾기_제거_요청("invalid", 즐겨찾기_노선); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); + } + + /** + * Given 지하철 즐겨찾기 노선을 생성하고, + * When 비로그인으로 즐겨찾기를 삭제 요청하면, + * Then 권한이 없어 (401)을 응답한다. + */ + @DisplayName("로그인되어 있지 않으면 삭제할 수 없다") + @Test + void deleteFavoriteIfNotLogin() { + + // given + String 즐겨찾기_노선 = 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역).header("location"); + + // when + ExtractableResponse response = 지하철_노선_즐겨찾기_제거_요청(즐겨찾기_노선); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); + } + + private Long 지하철_노선_생성_요청(String name, String color, Long upStation, Long downStation, int distance) { + Map lineCreateParams; + lineCreateParams = new HashMap<>(); + lineCreateParams.put("name", name); + lineCreateParams.put("color", color); + lineCreateParams.put("upStationId", upStation + ""); + lineCreateParams.put("downStationId", downStation + ""); + lineCreateParams.put("distance", distance + ""); + + return LineSteps.지하철_노선_생성_요청(관리자, lineCreateParams).jsonPath().getLong("id"); + } +} diff --git a/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java b/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java new file mode 100644 index 000000000..7363253c5 --- /dev/null +++ b/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java @@ -0,0 +1,46 @@ +package nextstep.subway.acceptance; + +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; +import org.springframework.http.MediaType; + +import java.util.HashMap; +import java.util.Map; + +public class FavoriteSteps { + public static ExtractableResponse 지하철_노선_즐겨찾기_생성(String accessToken, Long source, Long target) { + Map params = new HashMap<>(); + params.put("source", source + ""); + params.put("target", target + ""); + return given(accessToken) + .body(params) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/favorites") + .then().log().all().extract(); + } + + public static ExtractableResponse 지하철_노선_즐겨찾기_조회_요청(String accessToken) { + return given(accessToken) + .when().get("/favorites") + .then().log().all().extract(); + } + + public static ExtractableResponse 지하철_노선_즐겨찾기_제거_요청(String accessToken, String favoriteId) { + return given(accessToken) + .when().delete("/favorites/{favoriteId}", favoriteId) + .then().log().all().extract(); + } + + public static ExtractableResponse 지하철_노선_즐겨찾기_제거_요청(String favoriteId) { + return RestAssured.given().log().all() + .when().delete("/favorites/{favoriteId}", favoriteId) + .then().log().all().extract(); + } + + private static RequestSpecification given(String token) { + return RestAssured.given().log().all() + .auth().oauth2(token); + } +} From 9797b802e910f7b2900544ad76b187245dd628e2 Mon Sep 17 00:00:00 2001 From: epicarts <0505zxc@gmail.com> Date: Wed, 17 Aug 2022 15:59:13 +0900 Subject: [PATCH 05/16] =?UTF-8?q?feat:=20favorite=20/=20favorites=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EA=B0=9D=EC=B2=B4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1.=20=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/member/domain/Favorite.java | 40 +++++++++++++++++++ .../nextstep/member/domain/Favorites.java | 27 +++++++++++++ .../java/nextstep/member/domain/Member.java | 6 +++ 3 files changed, 73 insertions(+) create mode 100644 src/main/java/nextstep/member/domain/Favorite.java create mode 100644 src/main/java/nextstep/member/domain/Favorites.java diff --git a/src/main/java/nextstep/member/domain/Favorite.java b/src/main/java/nextstep/member/domain/Favorite.java new file mode 100644 index 000000000..4d4a30f9a --- /dev/null +++ b/src/main/java/nextstep/member/domain/Favorite.java @@ -0,0 +1,40 @@ +package nextstep.member.domain; + +import nextstep.subway.domain.Station; + +import javax.persistence.*; + +@Entity +public class Favorite { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "source_id") + private Station source; + + @ManyToOne + @JoinColumn(name = "target_id") + private Station target; + + protected Favorite() { + } + + public Favorite(Station source, Station target) { + this.source = source; + this.target = target; + } + + public Long getId() { + return id; + } + + public Station getSource() { + return source; + } + + public Station getTarget() { + return target; + } +} diff --git a/src/main/java/nextstep/member/domain/Favorites.java b/src/main/java/nextstep/member/domain/Favorites.java new file mode 100644 index 000000000..64c2b36af --- /dev/null +++ b/src/main/java/nextstep/member/domain/Favorites.java @@ -0,0 +1,27 @@ +package nextstep.member.domain; + +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +@Embeddable +public class Favorites { + @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true) + @JoinColumn(name = "member_id") + private List favorites = new ArrayList<>(); + + public List getFavorites() { + return this.favorites; + } + + public void add(Favorite favorite) { + this.favorites.add(favorite); + } + + public void delete(Favorite favorite) { + this.favorites.remove(favorite); + } +} diff --git a/src/main/java/nextstep/member/domain/Member.java b/src/main/java/nextstep/member/domain/Member.java index c065bc8f0..ed3b2e97d 100644 --- a/src/main/java/nextstep/member/domain/Member.java +++ b/src/main/java/nextstep/member/domain/Member.java @@ -11,6 +11,8 @@ public class Member { private String email; private String password; private Integer age; + @Embedded + private Favorites favorites = new Favorites(); @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "MEMBER_ROLE", @@ -56,6 +58,10 @@ public List getRoles() { return roles; } + public List getFavorites() { + return favorites.getFavorites(); + } + public void update(Member member) { this.email = member.email; this.password = member.password; From 3c347ac5217fa9dc0fa98ec548d2e26e952e5cf2 Mon Sep 17 00:00:00 2001 From: epicarts <0505zxc@gmail.com> Date: Wed, 17 Aug 2022 16:00:30 +0900 Subject: [PATCH 06/16] =?UTF-8?q?feat:=20FavoriteRepository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/member/domain/FavoriteRepository.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/nextstep/member/domain/FavoriteRepository.java diff --git a/src/main/java/nextstep/member/domain/FavoriteRepository.java b/src/main/java/nextstep/member/domain/FavoriteRepository.java new file mode 100644 index 000000000..696946817 --- /dev/null +++ b/src/main/java/nextstep/member/domain/FavoriteRepository.java @@ -0,0 +1,8 @@ +package nextstep.member.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface FavoriteRepository extends JpaRepository { +} From 17c229214d5a0de0870553146050109088b80989 Mon Sep 17 00:00:00 2001 From: epicarts <0505zxc@gmail.com> Date: Wed, 17 Aug 2022 17:32:13 +0900 Subject: [PATCH 07/16] =?UTF-8?q?feat/test:=20favorites=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/member/domain/Member.java | 7 ++ .../domain/exception/FavoriteException.java | 10 +++ .../nextstep/subway/unit/FavoritesTest.java | 89 +++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 src/main/java/nextstep/member/domain/exception/FavoriteException.java create mode 100644 src/test/java/nextstep/subway/unit/FavoritesTest.java diff --git a/src/main/java/nextstep/member/domain/Member.java b/src/main/java/nextstep/member/domain/Member.java index ed3b2e97d..0a21c1292 100644 --- a/src/main/java/nextstep/member/domain/Member.java +++ b/src/main/java/nextstep/member/domain/Member.java @@ -1,6 +1,9 @@ package nextstep.member.domain; +import nextstep.subway.domain.Station; + import javax.persistence.*; +import java.sql.Statement; import java.util.List; @Entity @@ -62,6 +65,10 @@ public List getFavorites() { return favorites.getFavorites(); } + public void addFavorites(Station source, Station target) { + favorites.add(new Favorite(source, target)); + } + public void update(Member member) { this.email = member.email; this.password = member.password; diff --git a/src/main/java/nextstep/member/domain/exception/FavoriteException.java b/src/main/java/nextstep/member/domain/exception/FavoriteException.java new file mode 100644 index 000000000..cb4f1dcd8 --- /dev/null +++ b/src/main/java/nextstep/member/domain/exception/FavoriteException.java @@ -0,0 +1,10 @@ +package nextstep.member.domain.exception; + +public class FavoriteException extends RuntimeException { + public static final String SAME_SOURCE_AND_TARGET = "즐겨찾기의 출발점과 도착역이 같을 수 없습니다."; + public static final String DUPLICATION_FAVORITE = "이미 등록되어있는 즐겨찾기입니다."; + + public FavoriteException(String message) { + super(message); + } +} diff --git a/src/test/java/nextstep/subway/unit/FavoritesTest.java b/src/test/java/nextstep/subway/unit/FavoritesTest.java new file mode 100644 index 000000000..e6f836352 --- /dev/null +++ b/src/test/java/nextstep/subway/unit/FavoritesTest.java @@ -0,0 +1,89 @@ +package nextstep.subway.unit; + +import nextstep.member.domain.Favorite; +import nextstep.member.domain.Favorites; +import nextstep.member.domain.exception.FavoriteException; +import nextstep.subway.domain.Station; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +class FavoritesTest { + @Nested + @DisplayName("성공") + class Success { + @DisplayName("즐겨찾기 추가") + @Test + void 즐겨찾기_역_추가() { + // given + Station 강남역 = new Station("강남역"); + Station 역삼역 = new Station("역삼역"); + Favorite favorite = new Favorite(강남역, 역삼역); + Favorites favorites = new Favorites(); + + // when + favorites.add(favorite); + + // then + assertThat(favorites.getFavorites()).contains(favorite); + } + + @DisplayName("즐겨찾기 삭제") + @Test + void 즐겨찾기_역_삭제() { + // given + Station 강남역 = new Station("강남역"); + Station 역삼역 = new Station("역삼역"); + Favorite favorite = new Favorite(강남역, 역삼역); + Favorites favorites = new Favorites(); + favorites.add(favorite); + + // when + favorites.delete(favorite); + + // then + assertThat(favorites.getFavorites()).doesNotContain(favorite); + } + } + + @Nested + @DisplayName("실패") + class Failure { + @DisplayName("즐겨찾기에 같은 출발역과 도착역이 등록되어 있으면 추가 실패") + @Test + void 중복_즐겨찾기_노선_추가_실패() { + // given + Station 강남역 = new Station("강남역"); + Station 역삼역 = new Station("역삼역"); + Favorites favorites = new Favorites(); + + // when + Favorite favorite = new Favorite(강남역, 역삼역); + favorites.add(favorite); + + // then + assertThatThrownBy(() -> favorites.add(favorite)) + .isInstanceOf(FavoriteException.class) + .hasMessage(FavoriteException.DUPLICATION_FAVORITE); + } + + @DisplayName("출발역과 도착역이 같으면 즐겨찾기 추가 실패") + @Test + void 즐겨찾기_같은역_추가_실패() { + // given + Station 강남역 = new Station("강남역"); + Favorites favorites = new Favorites(); + + // when + Favorite favorite = new Favorite(강남역, 강남역); + + // then + assertThatThrownBy(() -> favorites.add(favorite)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(FavoriteException.SAME_SOURCE_AND_TARGET); + } + } +} From 064a532ec3dda1d9c12f03b65d9060a2689e2988 Mon Sep 17 00:00:00 2001 From: epicarts <0505zxc@gmail.com> Date: Wed, 17 Aug 2022 17:56:27 +0900 Subject: [PATCH 08/16] =?UTF-8?q?feat=20favorites=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EB=B0=8F=20dto=20=EC=8A=A4=EC=BC=88?= =?UTF-8?q?=EB=A0=88=ED=86=A4=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/application/FavoriteService.java | 24 +++++++++++ .../application/dto/FavoriteRequest.java | 22 ++++++++++ .../application/dto/FavoriteResponse.java | 37 ++++++++++++++++ .../member/ui/FavoriteController.java | 42 +++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 src/main/java/nextstep/member/application/FavoriteService.java create mode 100644 src/main/java/nextstep/member/application/dto/FavoriteRequest.java create mode 100644 src/main/java/nextstep/member/application/dto/FavoriteResponse.java create mode 100644 src/main/java/nextstep/member/ui/FavoriteController.java diff --git a/src/main/java/nextstep/member/application/FavoriteService.java b/src/main/java/nextstep/member/application/FavoriteService.java new file mode 100644 index 000000000..2df9facf1 --- /dev/null +++ b/src/main/java/nextstep/member/application/FavoriteService.java @@ -0,0 +1,24 @@ +package nextstep.member.application; + +import nextstep.member.application.dto.FavoriteRequest; +import nextstep.member.application.dto.FavoriteResponse; +import nextstep.member.domain.FavoriteRepository; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class FavoriteService { + public Long saveFavorite(FavoriteRequest favoriteRequest) { + return null; + } + + public List findFavoriteResponses() { + return null; + } + + public void deleteFavorite(Long favoriteId) { + // TODO document why this method is empty + } +} diff --git a/src/main/java/nextstep/member/application/dto/FavoriteRequest.java b/src/main/java/nextstep/member/application/dto/FavoriteRequest.java new file mode 100644 index 000000000..30884892a --- /dev/null +++ b/src/main/java/nextstep/member/application/dto/FavoriteRequest.java @@ -0,0 +1,22 @@ +package nextstep.member.application.dto; + +public class FavoriteRequest { + private Long source; + private Long target; + + public FavoriteRequest() { + } + + public FavoriteRequest(Long source, Long target) { + this.source = source; + this.target = target; + } + + public Long getSource() { + return source; + } + + public Long getTarget() { + return target; + } +} diff --git a/src/main/java/nextstep/member/application/dto/FavoriteResponse.java b/src/main/java/nextstep/member/application/dto/FavoriteResponse.java new file mode 100644 index 000000000..982d646a1 --- /dev/null +++ b/src/main/java/nextstep/member/application/dto/FavoriteResponse.java @@ -0,0 +1,37 @@ +package nextstep.member.application.dto; + +import nextstep.member.domain.Favorite; +import nextstep.subway.applicaion.dto.StationResponse; +import nextstep.subway.domain.Station; + +public class FavoriteResponse { + private Long id; + private StationResponse source; + private StationResponse target; + + + public static FavoriteResponse of(Favorite favorite, Station source, Station target) { + return new FavoriteResponse(favorite.getId(), StationResponse.of(source), StationResponse.of(target)); + } + + public FavoriteResponse(Long id, StationResponse source, StationResponse target) { + this.id = id; + this.source = source; + this.target = target; + } + + public FavoriteResponse() { + } + + public Long getId() { + return id; + } + + public StationResponse getSource() { + return source; + } + + public StationResponse getTarget() { + return target; + } +} diff --git a/src/main/java/nextstep/member/ui/FavoriteController.java b/src/main/java/nextstep/member/ui/FavoriteController.java new file mode 100644 index 000000000..5f49bc476 --- /dev/null +++ b/src/main/java/nextstep/member/ui/FavoriteController.java @@ -0,0 +1,42 @@ +package nextstep.member.ui; + +import nextstep.auth.secured.Secured; +import nextstep.member.application.FavoriteService; +import nextstep.member.application.dto.FavoriteRequest; +import nextstep.member.application.dto.FavoriteResponse; +import nextstep.subway.applicaion.dto.LineRequest; +import nextstep.subway.applicaion.dto.LineResponse; +import nextstep.subway.applicaion.dto.SectionRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; +import java.util.List; + +@RestController +@RequestMapping("/favorites") +public class FavoriteController { + private final FavoriteService favoriteService; + + public FavoriteController(FavoriteService favoriteService) { + this.favoriteService = favoriteService; + } + + @PostMapping + public ResponseEntity createFavorite(@RequestBody FavoriteRequest favoriteRequest) { + Long favoriteId = favoriteService.saveFavorite(favoriteRequest); + return ResponseEntity.created(URI.create("/" + favoriteId)).build(); + } + + @GetMapping + public ResponseEntity> showFavorites() { + List responses = favoriteService.findFavoriteResponses(); + return ResponseEntity.ok().body(responses); + } + + @DeleteMapping("/{id}") + public ResponseEntity updateLine(@PathVariable Long id) { + favoriteService.deleteFavorite(id); + return ResponseEntity.noContent().build(); + } +} From 86b18b3238fd1208b2375ef85afbd5c844dff439 Mon Sep 17 00:00:00 2001 From: epicarts <0505zxc@gmail.com> Date: Wed, 17 Aug 2022 17:56:57 +0900 Subject: [PATCH 09/16] =?UTF-8?q?refactor:=20FavoriteException=20=EC=9D=B4?= =?UTF-8?q?=20IllegalArgumentException=EB=A5=BC=20=EC=83=81=EC=86=8D=20?= =?UTF-8?q?=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/member/domain/exception/FavoriteException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/nextstep/member/domain/exception/FavoriteException.java b/src/main/java/nextstep/member/domain/exception/FavoriteException.java index cb4f1dcd8..c4ddda67d 100644 --- a/src/main/java/nextstep/member/domain/exception/FavoriteException.java +++ b/src/main/java/nextstep/member/domain/exception/FavoriteException.java @@ -1,6 +1,6 @@ package nextstep.member.domain.exception; -public class FavoriteException extends RuntimeException { +public class FavoriteException extends IllegalArgumentException { public static final String SAME_SOURCE_AND_TARGET = "즐겨찾기의 출발점과 도착역이 같을 수 없습니다."; public static final String DUPLICATION_FAVORITE = "이미 등록되어있는 즐겨찾기입니다."; From 61ddba800808daa1afaf2cfcfbc2f8fa8f1d3898 Mon Sep 17 00:00:00 2001 From: YoungHo Choi <0505zxc@gmail.com> Date: Wed, 17 Aug 2022 21:28:54 +0900 Subject: [PATCH 10/16] =?UTF-8?q?test:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EC=9D=B8=EC=88=98=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceptance/FavoriteAcceptanceTest.java | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java index d9b5cdd9f..0b5596d7c 100644 --- a/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java @@ -133,11 +133,11 @@ public void setUp() { /** * Given 지하철 즐겨찾기 노선을 생성하고, * When 유효하지 않은 토큰이 즐겨찾기를 삭제 요청하면, - * Then 권한이 없어 (401)을 응답한다. + * Then 권한이 없음(401) 을 응답한다. */ @DisplayName("유효하지 않은 로그인 정보로 삭제할 수 없다") @Test - void 유효하지_않은_토큰으로요청() { + void 유효하지_않은_토큰으로_로그인_삭제_요청() { // given String 즐겨찾기_노선 = 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역).header("location"); @@ -151,11 +151,11 @@ public void setUp() { /** * Given 지하철 즐겨찾기 노선을 생성하고, * When 비로그인으로 즐겨찾기를 삭제 요청하면, - * Then 권한이 없어 (401)을 응답한다. + * Then 권한이 없음(401) 을 응답한다. */ @DisplayName("로그인되어 있지 않으면 삭제할 수 없다") @Test - void deleteFavoriteIfNotLogin() { + void 로그인하지_않은_즐겨찾기_삭제_요청() { // given String 즐겨찾기_노선 = 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역).header("location"); @@ -167,6 +167,25 @@ void deleteFavoriteIfNotLogin() { assertThat(response.statusCode()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); } + /** + * Givn 지하철 즐겨찾기 노선을 생성하고, + * When 다시 등록되어 있는 지하철 즐겨찾기 노선을 생성하면, + * Then 올바르지 않은 요청이기 떄문에 잘못된 요청(403)을 응답한다. + */ + @DisplayName("") + @Test + void 중복된_즐겨찾기_노선_생성_요청() { + + // given + 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역).header("location"); + + // when + ExtractableResponse response = 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + private Long 지하철_노선_생성_요청(String name, String color, Long upStation, Long downStation, int distance) { Map lineCreateParams; lineCreateParams = new HashMap<>(); From 7a1b8ea7f33afbf1ebb730805bb7d0047d75b17f Mon Sep 17 00:00:00 2001 From: YoungHo Choi <0505zxc@gmail.com> Date: Wed, 17 Aug 2022 23:46:05 +0900 Subject: [PATCH 11/16] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EC=97=86=EB=8A=94=20=EB=8B=A8=EC=88=9C=ED=95=9C=20?= =?UTF-8?q?=EC=A7=80=ED=95=98=EC=B2=A0=20=EB=85=B8=EC=84=A0=20=EC=A6=90?= =?UTF-8?q?=EA=B2=A8=EC=B0=BE=EA=B8=B0=20=EC=83=9D=EC=84=B1=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/application/FavoriteService.java | 33 ++++++++++++++++--- .../member/application/MemberService.java | 4 +++ .../java/nextstep/member/domain/Member.java | 23 +++++++++---- .../member/ui/FavoriteController.java | 26 ++++++++------- .../acceptance/FavoriteAcceptanceTest.java | 4 +-- 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src/main/java/nextstep/member/application/FavoriteService.java b/src/main/java/nextstep/member/application/FavoriteService.java index 2df9facf1..bf28d4bc9 100644 --- a/src/main/java/nextstep/member/application/FavoriteService.java +++ b/src/main/java/nextstep/member/application/FavoriteService.java @@ -1,17 +1,38 @@ package nextstep.member.application; +import java.util.List; import nextstep.member.application.dto.FavoriteRequest; import nextstep.member.application.dto.FavoriteResponse; +import nextstep.member.domain.Favorite; import nextstep.member.domain.FavoriteRepository; -import org.springframework.stereotype.Repository; +import nextstep.member.domain.LoginMember; +import nextstep.member.domain.Member; +import nextstep.subway.applicaion.StationService; +import nextstep.subway.domain.Station; import org.springframework.stereotype.Service; -import java.util.List; - @Service public class FavoriteService { - public Long saveFavorite(FavoriteRequest favoriteRequest) { - return null; + private final MemberService memberService; + private final StationService stationService; + private final FavoriteRepository favoriteRepository; + + public FavoriteService(MemberService memberService, StationService stationService, + FavoriteRepository favoriteRepository) { + this.memberService = memberService; + this.stationService = stationService; + this.favoriteRepository = favoriteRepository; + } + + public Long saveFavorite(LoginMember loginMember, FavoriteRequest favoriteRequest) { + Member member = memberService.findByEmail(loginMember.getEmail()); + + Station source = stationService.findById(favoriteRequest.getSource()); + Station target = stationService.findById(favoriteRequest.getTarget()); + + Favorite favorite = member.addFavorite(source, target); + + return favoriteRepository.save(favorite).getId(); } public List findFavoriteResponses() { @@ -21,4 +42,6 @@ public List findFavoriteResponses() { public void deleteFavorite(Long favoriteId) { // TODO document why this method is empty } + + } diff --git a/src/main/java/nextstep/member/application/MemberService.java b/src/main/java/nextstep/member/application/MemberService.java index b5985dd44..57a1b8ec3 100644 --- a/src/main/java/nextstep/member/application/MemberService.java +++ b/src/main/java/nextstep/member/application/MemberService.java @@ -46,4 +46,8 @@ public void deleteMember(Long id) { public void deleteMember(String email) { memberRepository.deleteByEmail(email); } + + public Member findByEmail(String email) { + return memberRepository.findByEmail(email).orElseThrow(IllegalArgumentException::new); + } } \ No newline at end of file diff --git a/src/main/java/nextstep/member/domain/Member.java b/src/main/java/nextstep/member/domain/Member.java index 0a21c1292..7c93c80df 100644 --- a/src/main/java/nextstep/member/domain/Member.java +++ b/src/main/java/nextstep/member/domain/Member.java @@ -1,10 +1,17 @@ package nextstep.member.domain; -import nextstep.subway.domain.Station; - -import javax.persistence.*; -import java.sql.Statement; import java.util.List; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import nextstep.subway.domain.Station; @Entity public class Member { @@ -65,8 +72,12 @@ public List getFavorites() { return favorites.getFavorites(); } - public void addFavorites(Station source, Station target) { - favorites.add(new Favorite(source, target)); + public Favorite addFavorite(Station source, Station target) { + Favorite favorite = new Favorite(source, target); + + favorites.add(favorite); + + return favorite; } public void update(Member member) { diff --git a/src/main/java/nextstep/member/ui/FavoriteController.java b/src/main/java/nextstep/member/ui/FavoriteController.java index 5f49bc476..f15a681a1 100644 --- a/src/main/java/nextstep/member/ui/FavoriteController.java +++ b/src/main/java/nextstep/member/ui/FavoriteController.java @@ -1,17 +1,20 @@ package nextstep.member.ui; -import nextstep.auth.secured.Secured; +import java.net.URI; +import java.util.List; +import nextstep.auth.authorization.AuthenticationPrincipal; import nextstep.member.application.FavoriteService; import nextstep.member.application.dto.FavoriteRequest; import nextstep.member.application.dto.FavoriteResponse; -import nextstep.subway.applicaion.dto.LineRequest; -import nextstep.subway.applicaion.dto.LineResponse; -import nextstep.subway.applicaion.dto.SectionRequest; +import nextstep.member.domain.LoginMember; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.net.URI; -import java.util.List; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/favorites") @@ -23,9 +26,10 @@ public FavoriteController(FavoriteService favoriteService) { } @PostMapping - public ResponseEntity createFavorite(@RequestBody FavoriteRequest favoriteRequest) { - Long favoriteId = favoriteService.saveFavorite(favoriteRequest); - return ResponseEntity.created(URI.create("/" + favoriteId)).build(); + public ResponseEntity createFavorite(@AuthenticationPrincipal LoginMember loginMember, + @RequestBody FavoriteRequest favoriteRequest) { + Long favoriteId = favoriteService.saveFavorite(loginMember, favoriteRequest); + return ResponseEntity.created(URI.create("/favorites/" + favoriteId)).build(); } @GetMapping diff --git a/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java index 0b5596d7c..d5f05e1d2 100644 --- a/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java @@ -61,10 +61,10 @@ public void setUp() { @Test void 즐겨찾기_노선을_생성한다() { // when - ExtractableResponse response = 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역); + ExtractableResponse response = 지하철_노선_즐겨찾기_생성(일반_사용자, 교대역, 양재역); // then - assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); } /** From eb5f9814ff39400d352a4955e870b7406d488163 Mon Sep 17 00:00:00 2001 From: epicarts <0505zxc@gmail.com> Date: Thu, 25 Aug 2022 13:51:08 +0900 Subject: [PATCH 12/16] =?UTF-8?q?feat:=20"=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=5F=EA=B0=99=EC=9D=80=EC=97=AD=5F=EC=B6=94=EA=B0=80=5F?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8"=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/member/domain/Favorite.java | 6 ++++++ src/main/java/nextstep/subway/domain/Station.java | 6 ++++++ src/test/java/nextstep/subway/unit/FavoritesTest.java | 7 +++---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/nextstep/member/domain/Favorite.java b/src/main/java/nextstep/member/domain/Favorite.java index 4d4a30f9a..b73f1887c 100644 --- a/src/main/java/nextstep/member/domain/Favorite.java +++ b/src/main/java/nextstep/member/domain/Favorite.java @@ -1,9 +1,12 @@ package nextstep.member.domain; +import nextstep.member.domain.exception.FavoriteException; import nextstep.subway.domain.Station; import javax.persistence.*; +import static nextstep.member.domain.exception.FavoriteException.SAME_SOURCE_AND_TARGET; + @Entity public class Favorite { @Id @@ -22,6 +25,9 @@ protected Favorite() { } public Favorite(Station source, Station target) { + if (source.isSame(target)) { + throw new FavoriteException(SAME_SOURCE_AND_TARGET); + } this.source = source; this.target = target; } diff --git a/src/main/java/nextstep/subway/domain/Station.java b/src/main/java/nextstep/subway/domain/Station.java index 79e394179..3910f6a08 100644 --- a/src/main/java/nextstep/subway/domain/Station.java +++ b/src/main/java/nextstep/subway/domain/Station.java @@ -4,6 +4,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import java.util.Objects; @Entity public class Station { @@ -26,4 +27,9 @@ public Long getId() { public String getName() { return name; } + + public boolean isSame(Station target) { + return Objects.equals(this.id, target.getId()) && + Objects.equals(this.name, target.getName()); + } } diff --git a/src/test/java/nextstep/subway/unit/FavoritesTest.java b/src/test/java/nextstep/subway/unit/FavoritesTest.java index e6f836352..dab19eb7e 100644 --- a/src/test/java/nextstep/subway/unit/FavoritesTest.java +++ b/src/test/java/nextstep/subway/unit/FavoritesTest.java @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import java.util.Optional; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -75,13 +77,10 @@ class Failure { void 즐겨찾기_같은역_추가_실패() { // given Station 강남역 = new Station("강남역"); - Favorites favorites = new Favorites(); // when - Favorite favorite = new Favorite(강남역, 강남역); - // then - assertThatThrownBy(() -> favorites.add(favorite)) + assertThatThrownBy(() -> new Favorite(강남역, 강남역)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(FavoriteException.SAME_SOURCE_AND_TARGET); } From ae1d3dff4d08e12ef469eab493a27d553b0425dd Mon Sep 17 00:00:00 2001 From: epicarts <0505zxc@gmail.com> Date: Thu, 25 Aug 2022 14:35:18 +0900 Subject: [PATCH 13/16] =?UTF-8?q?feat:=20"=EA=B0=99=EC=9D=80=20=EC=A6=90?= =?UTF-8?q?=EA=B2=A8=EC=B0=BE=EA=B8=B0=EB=A5=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=A0=EC=8B=9C=EC=97=90=20=EC=98=88=EC=99=B8=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D"=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/member/domain/Favorite.java | 4 ++++ .../java/nextstep/member/domain/Favorites.java | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/main/java/nextstep/member/domain/Favorite.java b/src/main/java/nextstep/member/domain/Favorite.java index b73f1887c..5b6694a99 100644 --- a/src/main/java/nextstep/member/domain/Favorite.java +++ b/src/main/java/nextstep/member/domain/Favorite.java @@ -43,4 +43,8 @@ public Station getSource() { public Station getTarget() { return target; } + + public boolean hasDuplicateFavorite(Station target, Station source) { + return this.target == target && this.source == source; + } } diff --git a/src/main/java/nextstep/member/domain/Favorites.java b/src/main/java/nextstep/member/domain/Favorites.java index 64c2b36af..da03cc466 100644 --- a/src/main/java/nextstep/member/domain/Favorites.java +++ b/src/main/java/nextstep/member/domain/Favorites.java @@ -1,5 +1,9 @@ package nextstep.member.domain; +import nextstep.member.domain.exception.FavoriteException; +import nextstep.subway.domain.Section; +import nextstep.subway.domain.Station; + import javax.persistence.CascadeType; import javax.persistence.Embeddable; import javax.persistence.JoinColumn; @@ -7,6 +11,8 @@ import java.util.ArrayList; import java.util.List; +import static nextstep.member.domain.exception.FavoriteException.DUPLICATION_FAVORITE; + @Embeddable public class Favorites { @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true) @@ -18,10 +24,20 @@ public List getFavorites() { } public void add(Favorite favorite) { + checkDuplicationFavorite(favorite); this.favorites.add(favorite); } public void delete(Favorite favorite) { this.favorites.remove(favorite); } + + private void checkDuplicationFavorite(Favorite favorite) { + favorites.stream() + .filter(it -> it.hasDuplicateFavorite(favorite.getTarget(), favorite.getSource())) + .findFirst() + .ifPresent(it -> { + throw new FavoriteException(DUPLICATION_FAVORITE); + }); + } } From 3061cc5239a2c89e5958cefe63d75e1e105e639c Mon Sep 17 00:00:00 2001 From: YoungHo Choi <0505zxc@gmail.com> Date: Sun, 4 Sep 2022 22:20:03 +0900 Subject: [PATCH 14/16] =?UTF-8?q?feat:=20"=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EC=82=AD=EC=A0=9C"=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/member/application/FavoriteService.java | 10 +++++++--- src/main/java/nextstep/member/domain/Member.java | 4 ++++ .../java/nextstep/member/ui/FavoriteController.java | 7 ++++--- .../java/nextstep/subway/acceptance/FavoriteSteps.java | 8 ++++---- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/java/nextstep/member/application/FavoriteService.java b/src/main/java/nextstep/member/application/FavoriteService.java index bf28d4bc9..277d38f7d 100644 --- a/src/main/java/nextstep/member/application/FavoriteService.java +++ b/src/main/java/nextstep/member/application/FavoriteService.java @@ -39,9 +39,13 @@ public List findFavoriteResponses() { return null; } - public void deleteFavorite(Long favoriteId) { - // TODO document why this method is empty - } + public void deleteFavorite(LoginMember loginMember, Long favoriteId) { + Favorite favorite = favoriteRepository.findById(favoriteId) + .orElseThrow(IllegalArgumentException::new); + Member member = memberService.findByEmail(loginMember.getEmail()); + member.removeFavorite(favorite); + favoriteRepository.delete(favorite); + } } diff --git a/src/main/java/nextstep/member/domain/Member.java b/src/main/java/nextstep/member/domain/Member.java index 7c93c80df..f04fed6a4 100644 --- a/src/main/java/nextstep/member/domain/Member.java +++ b/src/main/java/nextstep/member/domain/Member.java @@ -80,6 +80,10 @@ public Favorite addFavorite(Station source, Station target) { return favorite; } + public void removeFavorite(Favorite favorite) { + favorites.delete(favorite); + } + public void update(Member member) { this.email = member.email; this.password = member.password; diff --git a/src/main/java/nextstep/member/ui/FavoriteController.java b/src/main/java/nextstep/member/ui/FavoriteController.java index f15a681a1..0204d943a 100644 --- a/src/main/java/nextstep/member/ui/FavoriteController.java +++ b/src/main/java/nextstep/member/ui/FavoriteController.java @@ -38,9 +38,10 @@ public ResponseEntity> showFavorites() { return ResponseEntity.ok().body(responses); } - @DeleteMapping("/{id}") - public ResponseEntity updateLine(@PathVariable Long id) { - favoriteService.deleteFavorite(id); + @DeleteMapping("/{favoriteId}") + public ResponseEntity deleteFavorite(@AuthenticationPrincipal LoginMember loginMember, + @PathVariable Long favoriteId) { + favoriteService.deleteFavorite(loginMember, favoriteId); return ResponseEntity.noContent().build(); } } diff --git a/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java b/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java index 7363253c5..09c3c8ac5 100644 --- a/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java +++ b/src/test/java/nextstep/subway/acceptance/FavoriteSteps.java @@ -27,15 +27,15 @@ public class FavoriteSteps { .then().log().all().extract(); } - public static ExtractableResponse 지하철_노선_즐겨찾기_제거_요청(String accessToken, String favoriteId) { + public static ExtractableResponse 지하철_노선_즐겨찾기_제거_요청(String accessToken, String uri) { return given(accessToken) - .when().delete("/favorites/{favoriteId}", favoriteId) + .when().delete(uri) .then().log().all().extract(); } - public static ExtractableResponse 지하철_노선_즐겨찾기_제거_요청(String favoriteId) { + public static ExtractableResponse 지하철_노선_즐겨찾기_제거_요청(String uri) { return RestAssured.given().log().all() - .when().delete("/favorites/{favoriteId}", favoriteId) + .when().delete(uri) .then().log().all().extract(); } From 3048959c7f332d9e4f3c232c8e201a69869510f4 Mon Sep 17 00:00:00 2001 From: YoungHo Choi <0505zxc@gmail.com> Date: Sun, 4 Sep 2022 22:32:34 +0900 Subject: [PATCH 15/16] =?UTF-8?q?feat:=20"=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EC=A1=B0=ED=9A=8C"=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/member/application/FavoriteService.java | 10 ++++++++-- .../java/nextstep/member/ui/FavoriteController.java | 4 ++-- .../subway/acceptance/FavoriteAcceptanceTest.java | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/nextstep/member/application/FavoriteService.java b/src/main/java/nextstep/member/application/FavoriteService.java index 277d38f7d..92d872dde 100644 --- a/src/main/java/nextstep/member/application/FavoriteService.java +++ b/src/main/java/nextstep/member/application/FavoriteService.java @@ -1,6 +1,7 @@ package nextstep.member.application; import java.util.List; +import java.util.stream.Collectors; import nextstep.member.application.dto.FavoriteRequest; import nextstep.member.application.dto.FavoriteResponse; import nextstep.member.domain.Favorite; @@ -35,8 +36,13 @@ public Long saveFavorite(LoginMember loginMember, FavoriteRequest favoriteReques return favoriteRepository.save(favorite).getId(); } - public List findFavoriteResponses() { - return null; + public List findFavoriteResponses(LoginMember loginMember) { + Member member = memberService.findByEmail(loginMember.getEmail()); + List favorites = member.getFavorites(); + + return favorites.stream() + .map(it -> FavoriteResponse.of(it, it.getSource(), it.getTarget())) + .collect(Collectors.toList()); } public void deleteFavorite(LoginMember loginMember, Long favoriteId) { diff --git a/src/main/java/nextstep/member/ui/FavoriteController.java b/src/main/java/nextstep/member/ui/FavoriteController.java index 0204d943a..cc4dd9a5c 100644 --- a/src/main/java/nextstep/member/ui/FavoriteController.java +++ b/src/main/java/nextstep/member/ui/FavoriteController.java @@ -33,8 +33,8 @@ public ResponseEntity createFavorite(@AuthenticationPrincipal LoginMember } @GetMapping - public ResponseEntity> showFavorites() { - List responses = favoriteService.findFavoriteResponses(); + public ResponseEntity> showFavorites(@AuthenticationPrincipal LoginMember loginMember) { + List responses = favoriteService.findFavoriteResponses(loginMember); return ResponseEntity.ok().body(responses); } diff --git a/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java index d5f05e1d2..85128315c 100644 --- a/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/FavoriteAcceptanceTest.java @@ -83,7 +83,7 @@ public void setUp() { // then assertAll( - () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()), + () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()), () -> Assertions.assertThat(response.jsonPath().getList("source.name", String.class)).containsExactly("교대역"), () -> Assertions.assertThat(response.jsonPath().getList("target.name", String.class)).containsExactly("양재역") ); @@ -106,7 +106,7 @@ public void setUp() { // then assertAll( - () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()), + () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()), () -> Assertions.assertThat(response.jsonPath().getList("source.name", String.class)).containsExactly("교대역", "남부터미널역"), () -> Assertions.assertThat(response.jsonPath().getList("target.name", String.class)).containsExactly("양재역", "양재역") ); From fa2e69423433d1086a569b68b3409624eab9aa5d Mon Sep 17 00:00:00 2001 From: YoungHo Choi <0505zxc@gmail.com> Date: Sun, 4 Sep 2022 23:19:06 +0900 Subject: [PATCH 16/16] =?UTF-8?q?feat:=20"=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=EA=B0=80=20=EC=9A=94=EC=B2=AD=EC=8B=9C=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EB=B0=9C=EC=83=9D"=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/auth/secured/SecuredAnnotationChecker.java | 6 +++++- src/main/java/nextstep/member/ui/FavoriteController.java | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/nextstep/auth/secured/SecuredAnnotationChecker.java b/src/main/java/nextstep/auth/secured/SecuredAnnotationChecker.java index ee6f2e3f5..26b281b98 100644 --- a/src/main/java/nextstep/auth/secured/SecuredAnnotationChecker.java +++ b/src/main/java/nextstep/auth/secured/SecuredAnnotationChecker.java @@ -1,5 +1,7 @@ package nextstep.auth.secured; +import java.util.Optional; +import nextstep.auth.authentication.execption.AuthenticationException; import nextstep.auth.context.Authentication; import nextstep.auth.context.SecurityContextHolder; import org.aspectj.lang.JoinPoint; @@ -23,7 +25,9 @@ public void checkAuthorities(JoinPoint joinPoint) { Secured secured = method.getAnnotation(Secured.class); List values = Arrays.stream(secured.value()).collect(Collectors.toList()); - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) + .orElseThrow(() -> new AuthenticationException("인증에 실패하였습니다.")); + authentication.getAuthorities().stream() .filter(values::contains) .findFirst() diff --git a/src/main/java/nextstep/member/ui/FavoriteController.java b/src/main/java/nextstep/member/ui/FavoriteController.java index cc4dd9a5c..0b1c6c15a 100644 --- a/src/main/java/nextstep/member/ui/FavoriteController.java +++ b/src/main/java/nextstep/member/ui/FavoriteController.java @@ -3,6 +3,7 @@ import java.net.URI; import java.util.List; import nextstep.auth.authorization.AuthenticationPrincipal; +import nextstep.auth.secured.Secured; import nextstep.member.application.FavoriteService; import nextstep.member.application.dto.FavoriteRequest; import nextstep.member.application.dto.FavoriteResponse; @@ -26,6 +27,7 @@ public FavoriteController(FavoriteService favoriteService) { } @PostMapping + @Secured("ROLE_MEMBER") public ResponseEntity createFavorite(@AuthenticationPrincipal LoginMember loginMember, @RequestBody FavoriteRequest favoriteRequest) { Long favoriteId = favoriteService.saveFavorite(loginMember, favoriteRequest); @@ -33,12 +35,14 @@ public ResponseEntity createFavorite(@AuthenticationPrincipal LoginMember } @GetMapping + @Secured("ROLE_MEMBER") public ResponseEntity> showFavorites(@AuthenticationPrincipal LoginMember loginMember) { List responses = favoriteService.findFavoriteResponses(loginMember); return ResponseEntity.ok().body(responses); } @DeleteMapping("/{favoriteId}") + @Secured("ROLE_MEMBER") public ResponseEntity deleteFavorite(@AuthenticationPrincipal LoginMember loginMember, @PathVariable Long favoriteId) { favoriteService.deleteFavorite(loginMember, favoriteId);