diff --git a/BE/.gitignore b/BE/.gitignore index 46c5747..8ab92c2 100644 --- a/BE/.gitignore +++ b/BE/.gitignore @@ -126,4 +126,9 @@ gradle-app.setting # Java heap dump *.hprof +# application.yml +application.yml +application-local.yml +application-prod.yml + # End of https://www.toptal.com/developers/gitignore/api/macos,java,gradle diff --git a/BE/BOOT-INF/classes/static/docs/api.html b/BE/BOOT-INF/classes/static/docs/api.html index 175f17f..0e84063 100644 --- a/BE/BOOT-INF/classes/static/docs/api.html +++ b/BE/BOOT-INF/classes/static/docs/api.html @@ -456,19 +456,19 @@

Spring REST Docs

  • 단어 카드(Card)
  • 카드 내용(Content)
  • @@ -487,6 +487,7 @@

    HTTP request
    GET /api/note/1 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Accept: application/json
     Host: localhost:8080
    @@ -591,6 +592,7 @@

    HTTP request
    GET /api/note?page=0&size=5 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Accept: application/json
     Host: localhost:8080
    @@ -720,6 +722,7 @@

    HTTP request
    POST /api/note HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Content-Length: 47
     Host: localhost:8080
     
    @@ -822,6 +825,7 @@ 

    HTTP request
    PUT /api/note/1 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Content-Length: 33
     Host: localhost:8080
     
    @@ -983,6 +987,7 @@ 

    HTTP request
    DELETE /api/note/1 HTTP/1.1
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Host: localhost:8080
    @@ -1033,13 +1038,14 @@

    단어

    -

    1. [GET] 단어 단일 조회

    +

    1. [GET] 단어 단일 조회

    HTTP request

    GET /api/note/1/card/1 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Accept: application/json
     Host: localhost:8080
    @@ -1185,13 +1191,14 @@

    Response

    -

    2. [GET] 단어 모두 조회

    +

    2. [GET] 단어 모두 조회

    HTTP request

    GET /api/note/1/card?page=0&size=5 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Accept: application/json
     Host: localhost:8080
    @@ -1351,6 +1358,7 @@

    HTTP request
    POST /api/note/1/card HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Content-Length: 47
     Host: localhost:8080
     
    @@ -1490,13 +1498,14 @@ 

    Response

    -

    4. [PUT] 단어 카드 수정

    +

    4. [PUT] 단어 카드 수정

    HTTP request

    PUT /api/note/1/card/1 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Content-Length: 33
     Host: localhost:8080
     
    @@ -1640,12 +1649,13 @@ 

    Response

    -

    5. [DELETE] 단어 카드 삭제

    +

    5. [DELETE] 단어 카드 삭제

    HTTP request

    DELETE /api/note/1/card/1 HTTP/1.1
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Host: localhost:8080
    @@ -1738,13 +1748,14 @@


    -

    1. [GET] 내용 전체 조회

    +

    1. [GET] 내용 전체 조회

    HTTP request

    GET /api/note/1/card/1/content?page=0&size=5 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Accept: application/json
     Host: localhost:8080
    @@ -1884,13 +1895,14 @@

    Response

    -

    2. [POST] 내용 생성

    +

    2. [POST] 내용 생성

    HTTP request

    POST /api/note/1/card/1/content HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Content-Length: 71
     Host: localhost:8080
     
    @@ -2046,13 +2058,14 @@ 

    Response

    -

    3. [PUT] 내용 수정

    +

    3. [PUT] 내용 수정

    HTTP request

    PUT /api/note/1/card/1/content/1 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Content-Length: 70
     Host: localhost:8080
     
    @@ -2212,12 +2225,13 @@ 

    Response

    -

    4. [DELETE] 내용 삭제

    +

    4. [DELETE] 내용 삭제

    HTTP request

    DELETE /api/note/1/card/1/content/1 HTTP/1.1
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc3OTE1MjI2fQ.Q5UcK6Z7L_zLULI-1wXkbCoOfM4ELpKxKCVID8D1k08
     Host: localhost:8080
    @@ -2313,7 +2327,7 @@

    Respons diff --git a/BE/build.gradle b/BE/build.gradle index 75db93f..211aac0 100644 --- a/BE/build.gradle +++ b/BE/build.gradle @@ -20,11 +20,21 @@ repositories { mavenCentral() } +ext { + set('springCloudVersion', "2021.0.5") +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' - testImplementation 'org.testng:testng:7.1.0' + testImplementation 'org.testng:testng:7.7.0' compileOnly 'org.projectlombok:lombok' runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' @@ -32,9 +42,22 @@ dependencies { asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + // Feign Client + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + + // Wire Mock + testImplementation "org.springframework.cloud:spring-cloud-starter-contract-stub-runner" + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5', 'io.jsonwebtoken:jjwt-jackson:0.11.5' + // test containers testImplementation "org.testcontainers:junit-jupiter:1.17.2" testImplementation "org.testcontainers:mysql:1.17.2" + + // config processor + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" } ext { diff --git a/BE/src/docs/asciidoc/api.adoc b/BE/src/docs/asciidoc/api.adoc index ddb0545..cf01656 100644 --- a/BE/src/docs/asciidoc/api.adoc +++ b/BE/src/docs/asciidoc/api.adoc @@ -113,7 +113,7 @@ include::{snippets}/delete-note/http-response.adoc[] --- -[[resources-create-card]] +[[resources-get-one-card]] === 1. [GET] 단어 단일 조회 ==== HTTP request @@ -133,7 +133,7 @@ include::{snippets}/get-one-card/response-fields.adoc[] --- -[[resources-create-card]] +[[resources-get-all-cards]] === 2. [GET] 단어 모두 조회 ==== HTTP request @@ -179,6 +179,7 @@ include::{snippets}/create-card/response-fields.adoc[] --- +[[resources-update-card]] === 4. [PUT] 단어 카드 수정 ==== HTTP request @@ -198,6 +199,7 @@ include::{snippets}/update-card/response-fields.adoc[] --- +[[resources-delete-card]] === 5. [DELETE] 단어 카드 삭제 ==== HTTP request @@ -218,7 +220,7 @@ include::{snippets}/delete-card/response-fields.adoc[] --- -[[resources-create-content]] +[[resources-get-all-contents]] === 1. [GET] 내용 전체 조회 ==== HTTP request @@ -239,6 +241,7 @@ include::{snippets}/get-all-contents/response-fields.adoc[] --- +[[resources-create-content]] === 2. [POST] 내용 생성 ==== HTTP request @@ -261,6 +264,7 @@ include::{snippets}/create-content/response-fields.adoc[] --- +[[resources-update-content]] === 3. [PUT] 내용 수정 ==== HTTP request @@ -283,6 +287,7 @@ include::{snippets}/update-content/response-fields.adoc[] --- +[[resources-delete-content]] === 4. [DELETE] 내용 삭제 ==== HTTP request diff --git a/BE/src/main/java/dev/whatevernote/be/common/BaseResponse.java b/BE/src/main/java/dev/whatevernote/be/common/BaseResponse.java index 818e137..3258e1d 100644 --- a/BE/src/main/java/dev/whatevernote/be/common/BaseResponse.java +++ b/BE/src/main/java/dev/whatevernote/be/common/BaseResponse.java @@ -12,7 +12,7 @@ public BaseResponse(String code, String message, T data) { this.data = data; } - public BaseResponse(ResponseCodeAndMessages codeAndMessages, T data) { + public BaseResponse(CodeAndMessages codeAndMessages, T data) { this.code = codeAndMessages.getCode(); this.message = codeAndMessages.getMessage(); this.data = data; diff --git a/BE/src/main/java/dev/whatevernote/be/common/CodeAndMessages.java b/BE/src/main/java/dev/whatevernote/be/common/CodeAndMessages.java new file mode 100644 index 0000000..edd4307 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/common/CodeAndMessages.java @@ -0,0 +1,9 @@ +package dev.whatevernote.be.common; + +public interface CodeAndMessages { + + String getCode(); + + String getMessage(); + +} diff --git a/BE/src/main/java/dev/whatevernote/be/common/ResponseCodeAndMessages.java b/BE/src/main/java/dev/whatevernote/be/common/ResponseCodeAndMessages.java index f92737f..07c213f 100644 --- a/BE/src/main/java/dev/whatevernote/be/common/ResponseCodeAndMessages.java +++ b/BE/src/main/java/dev/whatevernote/be/common/ResponseCodeAndMessages.java @@ -1,6 +1,6 @@ package dev.whatevernote.be.common; -public enum ResponseCodeAndMessages { +public enum ResponseCodeAndMessages implements CodeAndMessages{ // NOTE @@ -23,6 +23,10 @@ public enum ResponseCodeAndMessages { CONTENT_REMOVE_SUCCESS("S-C003", "컨텐트 삭제를 성공했습니다."), CONTENT_RETRIEVE_DETAIL_SUCCESS("S-C004", "컨텐트 개별 상세 조회를 성공했습니다."), CONTENT_RETRIEVE_ALL_SUCCESS("S-C005", "컨텐트 전체 조회를 성공했습니다."), + + // OAUTH LOGIN + OAUTH_LOGIN_SUCCESS("S-L001", "소셜 로그인에 성공했습니다."), + REISSUE_ACCESS_TOKEN_SUCCESS("S-L002", "ACCESS TOKEN 재발급에 성공했습니다."), ; private final String code; diff --git a/BE/src/main/java/dev/whatevernote/be/config/WebConfig.java b/BE/src/main/java/dev/whatevernote/be/config/WebConfig.java index aa23df7..6d700c6 100644 --- a/BE/src/main/java/dev/whatevernote/be/config/WebConfig.java +++ b/BE/src/main/java/dev/whatevernote/be/config/WebConfig.java @@ -1,12 +1,25 @@ package dev.whatevernote.be.config; +import dev.whatevernote.be.login.AuthInterceptor; +import dev.whatevernote.be.login.LoginArgumentResolver; +import java.util.List; import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { + private final AuthInterceptor authInterceptor; + private final LoginArgumentResolver loginArgumentResolver; + + public WebConfig(AuthInterceptor authInterceptor, LoginArgumentResolver loginArgumentResolver) { + this.authInterceptor = authInterceptor; + this.loginArgumentResolver = loginArgumentResolver; + } + @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") @@ -14,4 +27,19 @@ public void addCorsMappings(CorsRegistry registry) { .allowedOrigins("*"); } + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor) + .addPathPatterns("/**") + .excludePathPatterns( + "/error/**", + "/login/kakao/**", + "/docs/**" + ); + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(loginArgumentResolver); + } } diff --git a/BE/src/main/java/dev/whatevernote/be/exception/BaseException.java b/BE/src/main/java/dev/whatevernote/be/exception/BaseException.java index 270da50..6324cc5 100644 --- a/BE/src/main/java/dev/whatevernote/be/exception/BaseException.java +++ b/BE/src/main/java/dev/whatevernote/be/exception/BaseException.java @@ -4,7 +4,7 @@ public class BaseException extends RuntimeException { private final String code; - protected BaseException(ErrorCodeAndMessages errorCodeAndMessages) { + public BaseException(ErrorCodeAndMessages errorCodeAndMessages) { super(errorCodeAndMessages.getMessage()); this.code = errorCodeAndMessages.getCode(); } diff --git a/BE/src/main/java/dev/whatevernote/be/exception/ErrorCodeAndMessages.java b/BE/src/main/java/dev/whatevernote/be/exception/ErrorCodeAndMessages.java index a5c1e1a..d618cf7 100644 --- a/BE/src/main/java/dev/whatevernote/be/exception/ErrorCodeAndMessages.java +++ b/BE/src/main/java/dev/whatevernote/be/exception/ErrorCodeAndMessages.java @@ -1,11 +1,20 @@ package dev.whatevernote.be.exception; -public enum ErrorCodeAndMessages { +import dev.whatevernote.be.common.CodeAndMessages; + +public enum ErrorCodeAndMessages implements CodeAndMessages { // 404 Not Found (존재하지 않는 리소스) E404_NOT_FOUND_NOTE("E-NF001", "노트의 아이디를 찾을 수 없습니다."), E404_NOT_FOUND_CARD("E-NF002", "카드의 아이디를 찾을 수 없습니다."), - E404_NOT_FOUND_CONTENT("E-NF003", "컨텐트의 아이디를 찾을 수 없습니다."); + E404_NOT_FOUND_CONTENT("E-NF003", "컨텐트의 아이디를 찾을 수 없습니다."), + E404_NOT_FOUND_MEMBER("E-NF004", "멤버 아이디를 찾을 수 없습니다."), + + // 400 Bad Request + E400_INVALID_JWT_TOKEN("E-BR001", "유효하지 않은 JWT Token 입니다."), + E400_NOT_LOGGED_IN_LOGIN_MEMBER("E-BR002", "로그인 되지 않은 멤버입니다."), + E400_NOT_MATCH_LOGIN_MEMBER("E-BR003", "로그인 멤버가 가진 노트가 아닙니다."), + ; private final String code; private final String message; diff --git a/BE/src/main/java/dev/whatevernote/be/exception/bad_request/InvalidJwtException.java b/BE/src/main/java/dev/whatevernote/be/exception/bad_request/InvalidJwtException.java new file mode 100644 index 0000000..e6eb08b --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/exception/bad_request/InvalidJwtException.java @@ -0,0 +1,11 @@ +package dev.whatevernote.be.exception.bad_request; + +import dev.whatevernote.be.exception.BaseException; +import dev.whatevernote.be.exception.ErrorCodeAndMessages; + +public class InvalidJwtException extends BaseException { + + public InvalidJwtException() { + super(ErrorCodeAndMessages.E400_INVALID_JWT_TOKEN); + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/exception/bad_request/NotLoggedInMemberException.java b/BE/src/main/java/dev/whatevernote/be/exception/bad_request/NotLoggedInMemberException.java new file mode 100644 index 0000000..de6de0d --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/exception/bad_request/NotLoggedInMemberException.java @@ -0,0 +1,11 @@ +package dev.whatevernote.be.exception.bad_request; + +import dev.whatevernote.be.exception.BaseException; +import dev.whatevernote.be.exception.ErrorCodeAndMessages; + +public class NotLoggedInMemberException extends BaseException { + + public NotLoggedInMemberException() { + super(ErrorCodeAndMessages.E400_NOT_LOGGED_IN_LOGIN_MEMBER); + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/exception/bad_request/NotMatchLoginMember.java b/BE/src/main/java/dev/whatevernote/be/exception/bad_request/NotMatchLoginMember.java new file mode 100644 index 0000000..cfd22d1 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/exception/bad_request/NotMatchLoginMember.java @@ -0,0 +1,11 @@ +package dev.whatevernote.be.exception.bad_request; + +import dev.whatevernote.be.exception.BaseException; +import dev.whatevernote.be.exception.ErrorCodeAndMessages; + +public class NotMatchLoginMember extends BaseException { + + public NotMatchLoginMember() { + super(ErrorCodeAndMessages.E400_NOT_MATCH_LOGIN_MEMBER); + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/exception/not_found/NotFoundMemberException.java b/BE/src/main/java/dev/whatevernote/be/exception/not_found/NotFoundMemberException.java new file mode 100644 index 0000000..351b981 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/exception/not_found/NotFoundMemberException.java @@ -0,0 +1,11 @@ +package dev.whatevernote.be.exception.not_found; + +import dev.whatevernote.be.exception.BaseException; +import dev.whatevernote.be.exception.ErrorCodeAndMessages; + +public class NotFoundMemberException extends BaseException { + + public NotFoundMemberException() { + super(ErrorCodeAndMessages.E404_NOT_FOUND_MEMBER); + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/AuthExtractor.java b/BE/src/main/java/dev/whatevernote/be/login/AuthExtractor.java new file mode 100644 index 0000000..ac7e4d8 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/AuthExtractor.java @@ -0,0 +1,31 @@ +package dev.whatevernote.be.login; + +import java.util.Enumeration; +import javax.servlet.http.HttpServletRequest; +import org.springframework.http.HttpHeaders; + +public class AuthExtractor { + + private static final String TOKEN_TYPE_BEARER = "Bearer"; + + public static String extract(HttpServletRequest request) { + Enumeration headers = request.getHeaders(HttpHeaders.AUTHORIZATION); + + while (headers.hasMoreElements()) { + String value = headers.nextElement(); + if (isBearerType(value)) { + return extractOAuthHeader(value); + } + } + return null; + } + + private static boolean isBearerType(String value) { + return value.toLowerCase().startsWith(TOKEN_TYPE_BEARER.toLowerCase()); + } + + private static String extractOAuthHeader(String value) { + return value.substring(TOKEN_TYPE_BEARER.length()).trim(); + } + +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/AuthInterceptor.java b/BE/src/main/java/dev/whatevernote/be/login/AuthInterceptor.java new file mode 100644 index 0000000..bc200d0 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/AuthInterceptor.java @@ -0,0 +1,40 @@ +package dev.whatevernote.be.login; + +import dev.whatevernote.be.login.service.provider.JwtProvider; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Slf4j +@Component +public class AuthInterceptor implements HandlerInterceptor { + + private static final String AUTH_TOKEN = "AUTH_TOKEN"; + + private final JwtProvider jwtProvider; + + public AuthInterceptor(JwtProvider jwtProvider) { + this.jwtProvider = jwtProvider; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (isPreflightRequest(request)) { + return true; + } + + String token = AuthExtractor.extract(request); + log.debug("REQUEST URI={}", request.getRequestURI()); + boolean isValidate = jwtProvider.validateToken(token); + request.setAttribute(AUTH_TOKEN, token); + log.debug("TOKEN VALIDATION STATUS = {}", isValidate); + return isValidate; + } + + private boolean isPreflightRequest(HttpServletRequest request) { + return request.getMethod().equals(HttpMethod.OPTIONS.name()); + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/Login.java b/BE/src/main/java/dev/whatevernote/be/login/Login.java new file mode 100644 index 0000000..c97fa0d --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/Login.java @@ -0,0 +1,12 @@ +package dev.whatevernote.be.login; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface Login { + +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/LoginArgumentResolver.java b/BE/src/main/java/dev/whatevernote/be/login/LoginArgumentResolver.java new file mode 100644 index 0000000..3ead88f --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/LoginArgumentResolver.java @@ -0,0 +1,38 @@ +package dev.whatevernote.be.login; + +import dev.whatevernote.be.exception.bad_request.NotLoggedInMemberException; +import dev.whatevernote.be.login.service.provider.JwtProvider; +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +public class LoginArgumentResolver implements HandlerMethodArgumentResolver { + + private static final String AUTH_TOKEN = "AUTH_TOKEN"; + private final JwtProvider jwtProvider; + + public LoginArgumentResolver(JwtProvider jwtProvider) { + this.jwtProvider = jwtProvider; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(Login.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); + String token = (String) Optional.ofNullable(request.getAttribute(AUTH_TOKEN)) + .orElseThrow(NotLoggedInMemberException::new); + + return Long.parseLong(jwtProvider.getAudience(token)); + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/client/KakaoMemberInfoClient.java b/BE/src/main/java/dev/whatevernote/be/login/client/KakaoMemberInfoClient.java new file mode 100644 index 0000000..75afbe6 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/client/KakaoMemberInfoClient.java @@ -0,0 +1,17 @@ +package dev.whatevernote.be.login.client; + +import dev.whatevernote.be.login.service.dto.response.KakaoAccountResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +@FeignClient(name = "kakao-member-info-client", url = "${oauth.kakao.resource-url}") +public interface KakaoMemberInfoClient { + + @GetMapping + KakaoAccountResponse call( + @RequestHeader("Content-Type") String contentType, + @RequestHeader("Authorization") String accessToken + ); + +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/client/KakaoTokenClient.java b/BE/src/main/java/dev/whatevernote/be/login/client/KakaoTokenClient.java new file mode 100644 index 0000000..848d54f --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/client/KakaoTokenClient.java @@ -0,0 +1,20 @@ +package dev.whatevernote.be.login.client; + +import dev.whatevernote.be.login.service.dto.response.KakaoAccessTokenResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "kakao-token-client", url = "${oauth.kakao.token-url}") +public interface KakaoTokenClient { + + @PostMapping + KakaoAccessTokenResponse call( + @RequestHeader("Content-Type") String contentType, + @RequestParam("grant_type") String grantType, + @RequestParam("client_id") String clientId, + @RequestParam("redirect_uri") String redirectUri, + @RequestParam("code") String code + ); +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/config/AuthConfig.java b/BE/src/main/java/dev/whatevernote/be/login/config/AuthConfig.java new file mode 100644 index 0000000..b7af0ad --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/config/AuthConfig.java @@ -0,0 +1,16 @@ +package dev.whatevernote.be.login.config; + +import dev.whatevernote.be.login.config.properties.KakaoProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(KakaoProperties.class) +public class AuthConfig { + + private final KakaoProperties kakaoProperties; + + public AuthConfig(KakaoProperties kakaoProperties) { + this.kakaoProperties = kakaoProperties; + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/config/JwtConfig.java b/BE/src/main/java/dev/whatevernote/be/login/config/JwtConfig.java new file mode 100644 index 0000000..dc2af17 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/config/JwtConfig.java @@ -0,0 +1,16 @@ +package dev.whatevernote.be.login.config; + +import dev.whatevernote.be.login.config.properties.JwtProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(JwtProperties.class) +public class JwtConfig { + + private final JwtProperties jwtProperties; + + public JwtConfig(JwtProperties jwtProperties) { + this.jwtProperties = jwtProperties; + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/config/KakaoAccountResponseDeserializer.java b/BE/src/main/java/dev/whatevernote/be/login/config/KakaoAccountResponseDeserializer.java new file mode 100644 index 0000000..353e6a4 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/config/KakaoAccountResponseDeserializer.java @@ -0,0 +1,39 @@ +package dev.whatevernote.be.login.config; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import dev.whatevernote.be.login.service.dto.response.KakaoAccountResponse; +import java.io.IOException; + +public class KakaoAccountResponseDeserializer extends JsonDeserializer { + + @Override + public KakaoAccountResponse deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode jsonNode = p.getCodec().readTree(p); + String uniqueId = jsonNode.get("id").asText(); + JsonNode kakaoAccountNode = jsonNode.get("kakao_account"); + + String nickname = kakaoAccountNode.get("profile") + .get("nickname") + .asText(); + + String profileImage = kakaoAccountNode.get("profile") + .get("profile_image_url") + .asText(); + + if (kakaoAccountNode.has("email")) { + String email = kakaoAccountNode.get("email").asText(); + boolean isEmailValid = kakaoAccountNode.get("is_email_valid").asBoolean(); + boolean isEmailVerified = kakaoAccountNode.get("is_email_verified").asBoolean(); + + if (isEmailValid && isEmailVerified) { + return new KakaoAccountResponse(uniqueId, nickname, profileImage, email); + } + return new KakaoAccountResponse(uniqueId, nickname, profileImage, null); + } + + return null; + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/config/OpenFeignConfig.java b/BE/src/main/java/dev/whatevernote/be/login/config/OpenFeignConfig.java new file mode 100644 index 0000000..65298cd --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/config/OpenFeignConfig.java @@ -0,0 +1,10 @@ +package dev.whatevernote.be.login.config; + +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableFeignClients("dev.whatevernote") +public class OpenFeignConfig { + +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/config/properties/JwtProperties.java b/BE/src/main/java/dev/whatevernote/be/login/config/properties/JwtProperties.java new file mode 100644 index 0000000..c5e0829 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/config/properties/JwtProperties.java @@ -0,0 +1,31 @@ +package dev.whatevernote.be.login.config.properties; + +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +@Getter +@ConstructorBinding +@ConfigurationProperties(prefix = "jwt") +public class JwtProperties { + + private final String secretKey; + private final Long accessExpireTime; + private final String accessTokenSubject; + private final Long refreshExpireTime; + private final String refreshTokenSubject; + private final String issuer; + private final String tokenType; + + public JwtProperties(String secretKey, Long accessExpireTime, String accessTokenSubject, + Long refreshExpireTime, String refreshTokenSubject, String issuer, String tokenType) { + this.secretKey = secretKey; + this.accessExpireTime = accessExpireTime; + this.accessTokenSubject = accessTokenSubject; + this.refreshExpireTime = refreshExpireTime; + this.refreshTokenSubject = refreshTokenSubject; + this.issuer = issuer; + this.tokenType = tokenType; + } + +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/config/properties/KakaoProperties.java b/BE/src/main/java/dev/whatevernote/be/login/config/properties/KakaoProperties.java new file mode 100644 index 0000000..1cf2e01 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/config/properties/KakaoProperties.java @@ -0,0 +1,30 @@ +package dev.whatevernote.be.login.config.properties; + +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +@Getter +@ConstructorBinding +@ConfigurationProperties(prefix = "oauth.kakao") +public class KakaoProperties { + + private final String loginPageUrl; + private final String responseType; + private final String clientId; + private final String redirectUri; + private final String tokenType; + private final String contentType; + private final String grantType; + + public KakaoProperties(String loginPageUrl, String responseType, String clientId, + String redirectUri, String tokenType, String contentType, String grantType) { + this.loginPageUrl = loginPageUrl; + this.responseType = responseType; + this.clientId = clientId; + this.redirectUri = redirectUri; + this.tokenType = tokenType; + this.contentType = contentType; + this.grantType = grantType; + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/repository/MemberRepository.java b/BE/src/main/java/dev/whatevernote/be/login/repository/MemberRepository.java new file mode 100644 index 0000000..8365cd5 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/repository/MemberRepository.java @@ -0,0 +1,12 @@ +package dev.whatevernote.be.login.repository; + +import dev.whatevernote.be.login.service.domain.Member; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { + + Optional findByUniqueId(String uniqueId); + + Optional getReferenceById(Long memberId); +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/service/LoginService.java b/BE/src/main/java/dev/whatevernote/be/login/service/LoginService.java new file mode 100644 index 0000000..6de6c28 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/service/LoginService.java @@ -0,0 +1,55 @@ +package dev.whatevernote.be.login.service; + +import dev.whatevernote.be.exception.not_found.NotFoundMemberException; +import dev.whatevernote.be.login.repository.MemberRepository; +import dev.whatevernote.be.login.service.domain.Member; +import dev.whatevernote.be.login.service.dto.response.KakaoAccountResponse; +import dev.whatevernote.be.login.service.dto.response.LoginResponse; +import dev.whatevernote.be.login.service.provider.JwtProvider; +import dev.whatevernote.be.login.service.provider.KakaoProvider; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +public class LoginService { + + private final JwtProvider jwtProvider; + private final KakaoProvider kakaoProvider; + private final MemberRepository memberRepository; + + public LoginService(JwtProvider jwtProvider, KakaoProvider kakaoProvider, MemberRepository memberRepository) { + this.jwtProvider = jwtProvider; + this.kakaoProvider = kakaoProvider; + this.memberRepository = memberRepository; + } + + @Transactional + public LoginResponse login(final String code) { + KakaoAccountResponse memberInfo = kakaoProvider.getMemberInformation(code); + Member member = memberRepository.findByUniqueId(memberInfo.getUniqueId()) + .orElseGet(() -> memberRepository.save(new Member( + memberInfo.getUniqueId(), + memberInfo.getNickname(), + memberInfo.getEmail(), + memberInfo.getProfileImage() + ))); + + return new LoginResponse( + member.getId(), + member.getNickname(), + member.getProfileImage(), + member.getEmail(), + jwtProvider.generateAccessToken(member.getId()), + jwtProvider.generateRefreshToken(member.getId()) + ); + } + + public String reIssueAccessToken(final Long memberId) { + Member member = memberRepository.findById(memberId) + .orElseThrow(NotFoundMemberException::new); + return jwtProvider.generateAccessToken(member.getId()); + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/service/domain/Member.java b/BE/src/main/java/dev/whatevernote/be/login/service/domain/Member.java new file mode 100644 index 0000000..eaf8777 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/service/domain/Member.java @@ -0,0 +1,33 @@ +package dev.whatevernote.be.login.service.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Member { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true) + private String uniqueId; + private String nickname; + private String email; + private String profileImage; + + public Member(String uniqueId, String nickname, String email, String profileImage) { + this.uniqueId = uniqueId; + this.nickname = nickname; + this.email = email; + this.profileImage = profileImage; + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/service/dto/response/KakaoAccessTokenResponse.java b/BE/src/main/java/dev/whatevernote/be/login/service/dto/response/KakaoAccessTokenResponse.java new file mode 100644 index 0000000..84b0da1 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/service/dto/response/KakaoAccessTokenResponse.java @@ -0,0 +1,16 @@ +package dev.whatevernote.be.login.service.dto.response; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Getter; + +@Getter +@JsonNaming(SnakeCaseStrategy.class) +public class KakaoAccessTokenResponse { + + private String tokenType; + private String accessToken; + private Long expiresIn; + private String refreshToken; + private Long refreshTokenExpiresIn; +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/service/dto/response/KakaoAccountResponse.java b/BE/src/main/java/dev/whatevernote/be/login/service/dto/response/KakaoAccountResponse.java new file mode 100644 index 0000000..82ebad0 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/service/dto/response/KakaoAccountResponse.java @@ -0,0 +1,22 @@ +package dev.whatevernote.be.login.service.dto.response; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import dev.whatevernote.be.login.config.KakaoAccountResponseDeserializer; +import lombok.Getter; + +@Getter +@JsonDeserialize(using = KakaoAccountResponseDeserializer.class) +public class KakaoAccountResponse { + + private final String uniqueId; + private final String nickname; + private final String profileImage; + private final String email; + + public KakaoAccountResponse(String uniqueId, String nickname, String profileImage, String email) { + this.uniqueId = uniqueId; + this.nickname = nickname; + this.profileImage = profileImage; + this.email = email; + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/service/dto/response/LoginResponse.java b/BE/src/main/java/dev/whatevernote/be/login/service/dto/response/LoginResponse.java new file mode 100644 index 0000000..4149889 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/service/dto/response/LoginResponse.java @@ -0,0 +1,23 @@ +package dev.whatevernote.be.login.service.dto.response; + +import lombok.Getter; + +@Getter +public class LoginResponse { + + private final Long memberId; + private final String nickname; + private final String profileImage; + private final String email; + private final String accessToken; + private final String refreshToken; + + public LoginResponse(Long memberId, String nickname, String profileImage, String email, String accessToken, String refreshToken) { + this.memberId = memberId; + this.nickname = nickname; + this.profileImage = profileImage; + this.email = email; + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/service/provider/JwtProvider.java b/BE/src/main/java/dev/whatevernote/be/login/service/provider/JwtProvider.java new file mode 100644 index 0000000..00757c2 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/service/provider/JwtProvider.java @@ -0,0 +1,75 @@ +package dev.whatevernote.be.login.service.provider; + +import dev.whatevernote.be.exception.bad_request.InvalidJwtException; +import dev.whatevernote.be.login.config.properties.JwtProperties; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import org.springframework.stereotype.Component; + +@Component +public class JwtProvider { + + private final JwtProperties jwtProperties; + private final SecretKey secretKey; + private final JwtParser jwtParser; + + public JwtProvider(JwtProperties jwtProperties) { + this.jwtProperties = jwtProperties; + this.secretKey = Keys.hmacShaKeyFor(jwtProperties.getSecretKey() + .getBytes(StandardCharsets.UTF_8)); + this.jwtParser = Jwts.parserBuilder() + .requireIssuer(jwtProperties.getIssuer()) + .setSigningKey(secretKey) + .build(); + } + + public String generateAccessToken(Long memberId) { + return generateToken( + memberId, + jwtProperties.getAccessTokenSubject(), + jwtProperties.getAccessExpireTime() + ); + } + + public String generateRefreshToken(Long memberId) { + return generateToken( + memberId, + jwtProperties.getRefreshTokenSubject(), + jwtProperties.getRefreshExpireTime() + ); + } + + private String generateToken(Long memberId, String accessTokenSubject, Long accessExpireTime) { + long now = System.currentTimeMillis(); + Date expiration = new Date(now + accessExpireTime); + + return Jwts.builder() + .setIssuer(jwtProperties.getIssuer()) + .setSubject(accessTokenSubject) + .setAudience(String.valueOf(memberId)) + .setExpiration(expiration) + .signWith(secretKey) + .compact(); + } + + public boolean validateToken(String token) { + try { + Jws claims = jwtParser.parseClaimsJws(token); + return !claims.getBody().getExpiration().before(new Date()); + } catch (JwtException | IllegalArgumentException exception) { + throw new InvalidJwtException(); + } + } + + public String getAudience(String token) { + return jwtParser.parseClaimsJws(token).getBody().getAudience(); + } + +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/service/provider/KakaoProvider.java b/BE/src/main/java/dev/whatevernote/be/login/service/provider/KakaoProvider.java new file mode 100644 index 0000000..e92331e --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/service/provider/KakaoProvider.java @@ -0,0 +1,42 @@ +package dev.whatevernote.be.login.service.provider; + +import dev.whatevernote.be.login.client.KakaoTokenClient; +import dev.whatevernote.be.login.client.KakaoMemberInfoClient; +import dev.whatevernote.be.login.config.properties.KakaoProperties; +import dev.whatevernote.be.login.service.dto.response.KakaoAccessTokenResponse; +import dev.whatevernote.be.login.service.dto.response.KakaoAccountResponse; +import org.springframework.stereotype.Component; + +@Component +public class KakaoProvider { + + private final KakaoTokenClient tokenClient; + private final KakaoMemberInfoClient memberInfoClient; + private final KakaoProperties properties; + + public KakaoProvider(KakaoTokenClient tokenClient, KakaoMemberInfoClient memberInfoClient, + KakaoProperties properties) { + this.tokenClient = tokenClient; + this.memberInfoClient = memberInfoClient; + this.properties = properties; + } + + public KakaoAccountResponse getMemberInformation(String code) { + KakaoAccessTokenResponse response = getAccessToken(code); + String accessToken = response.getAccessToken(); + + return memberInfoClient.call(properties.getContentType(), + String.format("%s %s", properties.getTokenType(), accessToken)); + } + + private KakaoAccessTokenResponse getAccessToken(String authCode) { + return tokenClient.call( + properties.getContentType(), + properties.getGrantType(), + properties.getClientId(), + properties.getRedirectUri(), + authCode + ); + } + +} diff --git a/BE/src/main/java/dev/whatevernote/be/login/web/LoginController.java b/BE/src/main/java/dev/whatevernote/be/login/web/LoginController.java new file mode 100644 index 0000000..7601183 --- /dev/null +++ b/BE/src/main/java/dev/whatevernote/be/login/web/LoginController.java @@ -0,0 +1,38 @@ +package dev.whatevernote.be.login.web; + +import static dev.whatevernote.be.common.ResponseCodeAndMessages.OAUTH_LOGIN_SUCCESS; +import static dev.whatevernote.be.common.ResponseCodeAndMessages.REISSUE_ACCESS_TOKEN_SUCCESS; + +import dev.whatevernote.be.common.BaseResponse; +import dev.whatevernote.be.login.Login; +import dev.whatevernote.be.login.service.LoginService; +import dev.whatevernote.be.login.service.dto.response.LoginResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/login") +public class LoginController { + + private final LoginService loginService; + + public LoginController(LoginService loginService) { + this.loginService = loginService; + } + + @GetMapping("/kakao") + public BaseResponse login(@RequestParam final String code) { + LoginResponse response = loginService.login(code); + return new BaseResponse<>(OAUTH_LOGIN_SUCCESS, response); + } + + @GetMapping("/re-issue") + public BaseResponse reIssueAccessToken(@Login final Long memberId) { + String response = loginService.reIssueAccessToken(memberId); + return new BaseResponse<>(REISSUE_ACCESS_TOKEN_SUCCESS, response); + } + + +} diff --git a/BE/src/main/java/dev/whatevernote/be/repository/CardRepository.java b/BE/src/main/java/dev/whatevernote/be/repository/CardRepository.java index 0a5dda4..c88a76b 100644 --- a/BE/src/main/java/dev/whatevernote/be/repository/CardRepository.java +++ b/BE/src/main/java/dev/whatevernote/be/repository/CardRepository.java @@ -1,10 +1,7 @@ package dev.whatevernote.be.repository; import dev.whatevernote.be.service.domain.Card; -import dev.whatevernote.be.service.domain.Note; -import dev.whatevernote.be.service.dto.request.CardRequestDto; import java.util.List; -import java.util.Optional; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; @@ -16,11 +13,11 @@ @Repository public interface CardRepository extends JpaRepository { - List findAllByOrderBySeq(); + List findAllByNoteIdOrderBySeqAsc(Integer noteId); - Slice findAllByNoteOrderBySeq(Pageable pageable, Note note); + Slice findAllByNoteIdOrderBySeqAsc(Pageable pageable, Integer noteId); - List findAllByNoteId(Integer noteId); + List findAllByNoteIdOrderBySeq(Integer noteId); @Modifying(clearAutomatically = true) @Query("update Card c SET c.deleted = TRUE WHERE c.note.id = :note_id") diff --git a/BE/src/main/java/dev/whatevernote/be/repository/ContentRepository.java b/BE/src/main/java/dev/whatevernote/be/repository/ContentRepository.java index d87e2df..6c1b5ef 100644 --- a/BE/src/main/java/dev/whatevernote/be/repository/ContentRepository.java +++ b/BE/src/main/java/dev/whatevernote/be/repository/ContentRepository.java @@ -14,7 +14,7 @@ public interface ContentRepository extends JpaRepository { Optional findFirstByOrderBySeq(); - List findAllByCardId(Long cardId); + List findAllByCardIdOrderBySeqAsc(Long cardId); List findAllByCardIdOrderBySeq(Long cardId); diff --git a/BE/src/main/java/dev/whatevernote/be/repository/NoteRepository.java b/BE/src/main/java/dev/whatevernote/be/repository/NoteRepository.java index 6b66799..7ff31a5 100644 --- a/BE/src/main/java/dev/whatevernote/be/repository/NoteRepository.java +++ b/BE/src/main/java/dev/whatevernote/be/repository/NoteRepository.java @@ -6,13 +6,17 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface NoteRepository extends JpaRepository { - Optional findFirstByOrderBySeq(); - List findAllByOrderBySeq(); +// @Query(value = "select n from Note n where n.member.id = :memberId order by n.seq limit 1") +// Optional findFirstByOrderBySeq(Long memberId); - Slice findAllByOrderBySeq(Pageable pageable); + Optional findFirstByMemberIdOrderBySeqAsc(Long memberId); + List findAllByMemberIdOrderBySeq(Long memberId); + + Slice findAllByMemberIdOrderBySeq(Long memberId, Pageable pageable); } diff --git a/BE/src/main/java/dev/whatevernote/be/service/CardService.java b/BE/src/main/java/dev/whatevernote/be/service/CardService.java index 3514234..3d30717 100644 --- a/BE/src/main/java/dev/whatevernote/be/service/CardService.java +++ b/BE/src/main/java/dev/whatevernote/be/service/CardService.java @@ -1,5 +1,6 @@ package dev.whatevernote.be.service; +import dev.whatevernote.be.exception.bad_request.NotMatchLoginMember; import dev.whatevernote.be.exception.not_found.NotFoundCardException; import dev.whatevernote.be.exception.not_found.NotFoundNoteException; import dev.whatevernote.be.repository.CardRepository; @@ -13,6 +14,7 @@ import dev.whatevernote.be.service.dto.response.CardResponseDto; import dev.whatevernote.be.service.dto.response.CardResponseDtos; import java.util.List; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Pageable; @@ -36,10 +38,17 @@ public CardService(CardRepository cardRepository, NoteRepository noteRepository, this.contentRepository = contentRepository; } + private static void checkValidMember(Long memberId, Note note) { + if (!Objects.equals(note.getMember().getId(), memberId)) { + throw new NotMatchLoginMember(); + } + } + @Transactional - public CardResponseDto create(CardRequestDto cardRequestDto, Integer noteId) { + public CardResponseDto create(CardRequestDto cardRequestDto, Integer noteId, Long memberId) { cardRequestDto = editSeq(cardRequestDto, noteId); Note note = findNoteById(noteId); + checkValidMember(memberId, note); final Card savedCard = cardRepository.save( Card.from(cardRequestDto, note) ); @@ -88,21 +97,21 @@ private CardRequestDto getCardRequestDto(CardRequestDto cardRequestDto, Integer } private List getCardsByNoteId(Integer noteId) { - List cards = cardRepository.findAllByNoteId(noteId); - cards.sort( - (o1, o2) -> (int) (o1.getSeq() - o2.getSeq()) - ); - return cards; + return cardRepository.findAllByNoteIdOrderBySeq(noteId); } - public CardResponseDtos findAll(final Pageable pageable, Integer noteId) { + public CardResponseDtos findAll(final Pageable pageable, Integer noteId, Long memberId) { Note note = findNoteById(noteId); - Slice cards = cardRepository.findAllByNoteOrderBySeq(pageable, note); + checkValidMember(memberId, note); + Slice cards = cardRepository.findAllByNoteIdOrderBySeqAsc(pageable, noteId); logger.debug("Now Page Number = {}, has Next = {}", cards.getNumber(), cards.hasNext()); return CardResponseDtos.from(cards, noteId); } - public CardDetailResponseDto findById(Integer noteId, Long cardId) { + public CardDetailResponseDto findById(Integer noteId, Long cardId, Long memberId) { + Note note = findNoteById(noteId); + checkValidMember(memberId, note); + Card card = cardRepository.findById(cardId) .orElseThrow(NotFoundCardException::new); List contents = findContentsById(cardId); @@ -110,13 +119,14 @@ public CardDetailResponseDto findById(Integer noteId, Long cardId) { } private List findContentsById(Long cardId) { - List contents = contentRepository.findAllByCardId(cardId); - contents.sort((o1, o2) -> (int) (o1.getSeq() - o2.getSeq())); - return contents; + return contentRepository.findAllByCardIdOrderBySeqAsc(cardId); } @Transactional - public CardResponseDto update(Integer noteId, Long cardId, CardRequestDto cardRequestDto) { + public CardResponseDto update(Integer noteId, Long cardId, CardRequestDto cardRequestDto, + Long memberId) { + Note note = findNoteById(noteId); + checkValidMember(memberId, note); Card card = findByCardId(cardId); logger.info("[BEFORE CARD UPDATE] card id = {}, note id = {}, title = {}, seq = {}", cardId, noteId, card.getTitle(), card.getSeq()); @@ -146,7 +156,9 @@ private Card findByCardId(Long cardId) { } @Transactional - public void delete(Long cardId) { + public void delete(Integer noteId, Long cardId, Long memberId) { + Note note = findNoteById(noteId); + checkValidMember(memberId, note); Card card = findByCardId(cardId); contentRepository.deleteAll(cardId); logger.debug("[CONTENT ALL DELETED] (CARD ID = {})'s contents delete", card.getId()); diff --git a/BE/src/main/java/dev/whatevernote/be/service/ContentService.java b/BE/src/main/java/dev/whatevernote/be/service/ContentService.java index 5cf4fff..d12d5ed 100644 --- a/BE/src/main/java/dev/whatevernote/be/service/ContentService.java +++ b/BE/src/main/java/dev/whatevernote/be/service/ContentService.java @@ -1,15 +1,20 @@ package dev.whatevernote.be.service; +import dev.whatevernote.be.exception.bad_request.NotMatchLoginMember; import dev.whatevernote.be.exception.not_found.NotFoundCardException; import dev.whatevernote.be.exception.not_found.NotFoundContentException; +import dev.whatevernote.be.exception.not_found.NotFoundNoteException; import dev.whatevernote.be.repository.CardRepository; import dev.whatevernote.be.repository.ContentRepository; +import dev.whatevernote.be.repository.NoteRepository; import dev.whatevernote.be.service.domain.Card; import dev.whatevernote.be.service.domain.Content; +import dev.whatevernote.be.service.domain.Note; import dev.whatevernote.be.service.dto.request.ContentRequestDto; import dev.whatevernote.be.service.dto.response.ContentResponseDto; import dev.whatevernote.be.service.dto.response.ContentResponseDtos; import java.util.List; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Pageable; @@ -23,21 +28,21 @@ public class ContentService { private static final Logger logger = LoggerFactory.getLogger(ContentService.class); private static final long DEFAULT_RANGE = 1_000L; + private final NoteRepository noteRepository; private final CardRepository cardRepository; private final ContentRepository contentRepository; - public ContentService(CardRepository cardRepository, ContentRepository contentRepository) { - + public ContentService(NoteRepository noteRepository, CardRepository cardRepository, ContentRepository contentRepository) { + this.noteRepository = noteRepository; this.cardRepository = cardRepository; this.contentRepository = contentRepository; } @Transactional - public ContentResponseDto create(ContentRequestDto contentRequestDto, Long cardId) { + public ContentResponseDto create(ContentRequestDto contentRequestDto, Integer noteId, final Long cardId, final Long memberId) { + checkValidMember(memberId, noteId); contentRequestDto = editSeq(contentRequestDto, cardId); - Card card = cardRepository.findById(cardId) - .orElseThrow(NotFoundCardException::new); - + Card card = findCardById(cardId); Content content = Content.from(contentRequestDto, card); final Content savedContent = contentRepository.save(content); @@ -57,20 +62,18 @@ private ContentRequestDto editSeq(ContentRequestDto contentRequestDto, Long card } private ContentRequestDto getContentRequestDtoWithFirstSeq(ContentRequestDto contentRequestDto, Long cardId) { - List contents = contentRepository.findAllByCardId(cardId); + List contents = contentRepository.findAllByCardIdOrderBySeqAsc(cardId); if (contents.isEmpty()) { return new ContentRequestDto(contentRequestDto.getInfo(), DEFAULT_RANGE, contentRequestDto.getIsImage()); } - contents.sort((o1, o2) -> Math.toIntExact(o1.getSeq() - o2.getSeq())); Content content = contents.get(0); return new ContentRequestDto(contentRequestDto.getInfo(), content.getSeq() / 2, contentRequestDto.getIsImage()); } private ContentRequestDto getContentRequestDto(ContentRequestDto contentRequestDto, Long cardId) { - List contents = contentRepository.findAllByCardId(cardId); - contents.sort((o1, o2) -> (int) (o1.getSeq() - o2.getSeq())); + List contents = contentRepository.findAllByCardIdOrderBySeqAsc(cardId); if (contents.isEmpty()) { return new ContentRequestDto(contentRequestDto.getInfo(), DEFAULT_RANGE, contentRequestDto.getIsImage()); @@ -87,25 +90,23 @@ private ContentRequestDto getContentRequestDto(ContentRequestDto contentRequestD (contents.size() + 1) * DEFAULT_RANGE, contentRequestDto.getIsImage()); } - public ContentResponseDto findById(Long contentId) { - - Content content = contentRepository.findById(contentId) - .orElseThrow(NotFoundContentException::new); - + public ContentResponseDto findById(Integer noteId, Long contentId, Long memberId) { + checkValidMember(memberId, noteId); + Content content = findContentById(contentId); return ContentResponseDto.from(content); } - public ContentResponseDtos findAll(Pageable pageable, Long cardId) { - + public ContentResponseDtos findAll(Pageable pageable, Integer noteId, Long cardId, Long memberId) { + checkValidMember(memberId, noteId); Slice contents = contentRepository.findAllByCardIdOrderBySeq(pageable, cardId); return ContentResponseDtos.from(contents); } @Transactional - public ContentResponseDto update(Long cardId, Long contentId, ContentRequestDto contentRequestDto) { - - Content content = contentRepository.findById(contentId) - .orElseThrow(NotFoundContentException::new); + public ContentResponseDto update(Integer noteId, Long cardId, Long contentId, + ContentRequestDto contentRequestDto, Long memberId) { + checkValidMember(memberId, noteId); + Content content = findContentById(contentId); if (contentRequestDto.getSeq() != null) { List contents = contentRepository.findAllByCardIdOrderBySeq(cardId); @@ -129,9 +130,31 @@ public ContentResponseDto update(Long cardId, Long contentId, ContentRequestDto } @Transactional - public void delete(Long contentId) { - + public void delete(Integer noteId, Long contentId, Long memberId) { + checkValidMember(memberId, noteId); contentRepository.deleteById(contentId); logger.debug("[CONTENT DELETED] content id = {}", contentId); } + + private void checkValidMember(Long memberId, Integer noteId) { + Note note = findNoteById(noteId); + if (!Objects.equals(note.getMember().getId(), memberId)) { + throw new NotMatchLoginMember(); + } + } + + private Note findNoteById(Integer noteId) { + return noteRepository.findById(noteId) + .orElseThrow(NotFoundNoteException::new); + } + + private Card findCardById(Long cardId) { + return cardRepository.findById(cardId) + .orElseThrow(NotFoundCardException::new); + } + + private Content findContentById(Long contentId) { + return contentRepository.findById(contentId) + .orElseThrow(NotFoundContentException::new); + } } diff --git a/BE/src/main/java/dev/whatevernote/be/service/NoteService.java b/BE/src/main/java/dev/whatevernote/be/service/NoteService.java index 519ce11..540817a 100644 --- a/BE/src/main/java/dev/whatevernote/be/service/NoteService.java +++ b/BE/src/main/java/dev/whatevernote/be/service/NoteService.java @@ -1,6 +1,9 @@ package dev.whatevernote.be.service; +import dev.whatevernote.be.exception.bad_request.NotMatchLoginMember; import dev.whatevernote.be.exception.not_found.NotFoundNoteException; +import dev.whatevernote.be.login.repository.MemberRepository; +import dev.whatevernote.be.login.service.domain.Member; import dev.whatevernote.be.repository.CardRepository; import dev.whatevernote.be.repository.ContentRepository; import dev.whatevernote.be.repository.NoteRepository; @@ -10,6 +13,7 @@ import dev.whatevernote.be.service.dto.response.NoteResponseDto; import dev.whatevernote.be.service.dto.response.NoteResponseDtos; import java.util.List; +import java.util.Objects; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,23 +28,33 @@ public class NoteService { private static final Logger logger = LoggerFactory.getLogger(NoteService.class); private static final int DEFAULT_RANGE = 1_000; + private final MemberRepository memberRepository; private final NoteRepository noteRepository; private final CardRepository cardRepository; - private final ContentRepository contentRepository; - public NoteService(NoteRepository noteRepository, - CardRepository cardRepository, - ContentRepository contentRepository) { + public NoteService(MemberRepository memberRepository, NoteRepository noteRepository, + CardRepository cardRepository, ContentRepository contentRepository) { + this.memberRepository = memberRepository; this.noteRepository = noteRepository; this.cardRepository = cardRepository; this.contentRepository = contentRepository; } - public NoteResponseDto findById(final Integer noteId) { + private final ContentRepository contentRepository; + + public NoteResponseDto findById(final Integer noteId, final Long memberId) { + logger.debug("note ID = {}, member ID = {}", noteId, memberId); Note note = findNoteById(noteId); + checkValidMember(memberId, note); return NoteResponseDto.from(note); } + private static void checkValidMember(Long memberId, Note note) { + if (!Objects.equals(note.getMember().getId(), memberId)) { + throw new NotMatchLoginMember(); + } + } + private Note findNoteById(Integer noteId) { return noteRepository.findById(noteId) .orElseThrow(NotFoundNoteException::new); @@ -48,55 +62,58 @@ private Note findNoteById(Integer noteId) { @Transactional - public NoteResponseDto create(NoteRequestDto noteRequestDto) { - noteRequestDto = editSeq(noteRequestDto); - final Note savedNote = noteRepository.save(Note.from(noteRequestDto)); + public NoteResponseDto create(NoteRequestDto noteRequestDto, final Long memberId) { + noteRequestDto = editSeq(noteRequestDto, memberId); + Member member = memberRepository.getReferenceById(memberId) + .orElseThrow(NotMatchLoginMember::new); + final Note savedNote = noteRepository.save(Note.from(noteRequestDto, member)); logger.debug("[CREATE Note] ID = {}, SEQ = {}", savedNote.getId(), savedNote.getSeq()); return NoteResponseDto.from(savedNote); } - public NoteResponseDtos findAll(Pageable pageable) { - Slice notes = noteRepository.findAllByOrderBySeq(pageable); + public NoteResponseDtos findAll(Long memberId, Pageable pageable) { + Slice notes = noteRepository.findAllByMemberIdOrderBySeq(memberId, pageable); return NoteResponseDtos.from(notes); } @Transactional - public NoteResponseDto update(Integer updateNoteId, NoteRequestDto noteRequestDto) { + public NoteResponseDto update(Integer updateNoteId, NoteRequestDto noteRequestDto, Long memberId) { Note note = findNoteById(updateNoteId); logger.debug("현재 노트의 Seq={}", note.getSeq()); if (noteRequestDto.getTitle() != null) { note.updateTitle(noteRequestDto.getTitle()); } else { - List notes = noteRepository.findAllByOrderBySeq(); + List notes = noteRepository.findAllByMemberIdOrderBySeq(memberId); int idx = notes.indexOf(note); if (noteRequestDto.getSeq() == idx + 1) { return NoteResponseDto.from(note); } logger.debug("이전 노트의 개수 = {}, 현재 NoteRequestDto의 Seq ={}", notes.indexOf(note), noteRequestDto.getSeq()); - note.updateSeq(editSeq(noteRequestDto)); + note.updateSeq(editSeq(noteRequestDto, memberId)); } return NoteResponseDto.from(note); } - private NoteRequestDto editSeq(NoteRequestDto noteRequestDto) { + private NoteRequestDto editSeq(NoteRequestDto noteRequestDto, Long memberId) { Integer noteDtoSeq = noteRequestDto.getSeq(); if (noteDtoSeq == null || noteDtoSeq == 0) { - return getNoteRequestDtoWithFirstSeq(noteRequestDto); + return getNoteRequestDtoWithFirstSeq(noteRequestDto, memberId); } - return getNoteRequestDto(noteRequestDto); + return getNoteRequestDto(noteRequestDto, memberId); } - private NoteRequestDto getNoteRequestDtoWithFirstSeq(NoteRequestDto noteRequestDto) { + private NoteRequestDto getNoteRequestDtoWithFirstSeq(NoteRequestDto noteRequestDto, + Long memberId) { String noteDtoTitle = noteRequestDto.getTitle(); - Optional note = noteRepository.findFirstByOrderBySeq(); + Optional note = noteRepository.findFirstByMemberIdOrderBySeqAsc(memberId); return note.map(value -> new NoteRequestDto(value.getSeq() / 2, noteDtoTitle)) .orElseGet(() -> new NoteRequestDto(DEFAULT_RANGE, noteDtoTitle)); } - private NoteRequestDto getNoteRequestDto(NoteRequestDto noteRequestDto) { + private NoteRequestDto getNoteRequestDto(NoteRequestDto noteRequestDto, Long memberId) { Integer noteDtoSeq = noteRequestDto.getSeq(); - List notes = noteRepository.findAllByOrderBySeq(); + List notes = noteRepository.findAllByMemberIdOrderBySeq(memberId); if (notes.isEmpty()) { return new NoteRequestDto(DEFAULT_RANGE, noteRequestDto.getTitle()); } @@ -112,9 +129,11 @@ private NoteRequestDto getNoteRequestDto(NoteRequestDto noteRequestDto) { } @Transactional - public void delete(Integer noteId) { + public void delete(Integer noteId, Long memberId) { Note note = findNoteById(noteId); - List cards = cardRepository.findAllByNoteId(noteId); + checkValidMember(memberId, note); + + List cards = cardRepository.findAllByNoteIdOrderBySeq(noteId); for (Card card : cards) { contentRepository.deleteAll(card.getId()); logger.debug("[CONTENT ALL DELETED] (CARD ID = {})'s contents delete", card.getId()); diff --git a/BE/src/main/java/dev/whatevernote/be/service/domain/Note.java b/BE/src/main/java/dev/whatevernote/be/service/domain/Note.java index 19ad551..eb31ca2 100644 --- a/BE/src/main/java/dev/whatevernote/be/service/domain/Note.java +++ b/BE/src/main/java/dev/whatevernote/be/service/domain/Note.java @@ -1,12 +1,16 @@ package dev.whatevernote.be.service.domain; import dev.whatevernote.be.common.BaseEntity; +import dev.whatevernote.be.login.service.domain.Member; import dev.whatevernote.be.service.dto.request.NoteRequestDto; import javax.persistence.Column; 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 javax.persistence.ManyToOne; import javax.validation.constraints.NotNull; import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; @@ -26,11 +30,16 @@ public class Note extends BaseEntity { private String title; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + protected Note() {} - private Note(Integer seq, String title) { + private Note(Integer seq, String title, Member member) { this.seq = seq; this.title = title; + this.member = member; } public Integer getId() { @@ -45,8 +54,12 @@ public String getTitle() { return title; } - public static Note from(NoteRequestDto noteRequestDto) { - return new Note(noteRequestDto.getSeq(), noteRequestDto.getTitle()); + public Member getMember() { + return member; + } + + public static Note from(NoteRequestDto noteRequestDto, Member member) { + return new Note(noteRequestDto.getSeq(), noteRequestDto.getTitle(), member); } diff --git a/BE/src/main/java/dev/whatevernote/be/web/controller/CardController.java b/BE/src/main/java/dev/whatevernote/be/web/controller/CardController.java index d0fe939..5e2fee8 100644 --- a/BE/src/main/java/dev/whatevernote/be/web/controller/CardController.java +++ b/BE/src/main/java/dev/whatevernote/be/web/controller/CardController.java @@ -7,6 +7,7 @@ import static dev.whatevernote.be.common.ResponseCodeAndMessages.CARD_RETRIEVE_DETAIL_SUCCESS; import dev.whatevernote.be.common.BaseResponse; +import dev.whatevernote.be.login.Login; import dev.whatevernote.be.service.CardService; import dev.whatevernote.be.service.dto.request.CardRequestDto; import dev.whatevernote.be.service.dto.response.CardDetailResponseDto; @@ -33,32 +34,35 @@ public CardController(CardService cardService) { } @PostMapping - public BaseResponse create(@RequestBody final CardRequestDto cardRequestDto, @PathVariable final Integer noteId) { - CardResponseDto cardResponseDto = cardService.create(cardRequestDto, noteId); + public BaseResponse create(@RequestBody final CardRequestDto cardRequestDto, @PathVariable final Integer noteId, + @Login final Long memberId) { + CardResponseDto cardResponseDto = cardService.create(cardRequestDto, noteId, memberId); return new BaseResponse<>(CARD_CREATE_SUCCESS, cardResponseDto); } @GetMapping("/{cardId}") - public BaseResponse findById(@PathVariable final Integer noteId, @PathVariable final Long cardId) { - CardDetailResponseDto cardDetailResponseDto = cardService.findById(noteId, cardId); + public BaseResponse findById(@PathVariable final Integer noteId, @PathVariable final Long cardId, + @Login final Long memberId) { + CardDetailResponseDto cardDetailResponseDto = cardService.findById(noteId, cardId, memberId); return new BaseResponse<>(CARD_RETRIEVE_DETAIL_SUCCESS, cardDetailResponseDto); } @GetMapping - public BaseResponse findAll(final Pageable pageable, @PathVariable final Integer noteId) { - CardResponseDtos cardResponseDtos = cardService.findAll(pageable, noteId); + public BaseResponse findAll(final Pageable pageable, @PathVariable final Integer noteId, @Login final Long memberId) { + CardResponseDtos cardResponseDtos = cardService.findAll(pageable, noteId, memberId); return new BaseResponse<>(CARD_RETRIEVE_ALL_SUCCESS, cardResponseDtos); } @PutMapping("/{cardId}") public BaseResponse update(@PathVariable final Integer noteId, - @PathVariable final Long cardId, @RequestBody final CardRequestDto cardRequestDto) { - return new BaseResponse<>(CARD_MODIFY_SUCCESS, cardService.update(noteId, cardId, cardRequestDto)); + @PathVariable final Long cardId, @RequestBody final CardRequestDto cardRequestDto, @Login final Long memberId) { + return new BaseResponse<>(CARD_MODIFY_SUCCESS, cardService.update(noteId, cardId, cardRequestDto, memberId)); } @DeleteMapping("/{cardId}") - public BaseResponse delete(@PathVariable final Long cardId) { - cardService.delete(cardId); + public BaseResponse delete(@PathVariable final Integer noteId, @PathVariable final Long cardId, + @Login final Long memberId) { + cardService.delete(noteId, cardId, memberId); return new BaseResponse<>(CARD_REMOVE_SUCCESS, null); } diff --git a/BE/src/main/java/dev/whatevernote/be/web/controller/ContentController.java b/BE/src/main/java/dev/whatevernote/be/web/controller/ContentController.java index 3102216..3af286d 100644 --- a/BE/src/main/java/dev/whatevernote/be/web/controller/ContentController.java +++ b/BE/src/main/java/dev/whatevernote/be/web/controller/ContentController.java @@ -7,6 +7,7 @@ import static dev.whatevernote.be.common.ResponseCodeAndMessages.CONTENT_RETRIEVE_DETAIL_SUCCESS; import dev.whatevernote.be.common.BaseResponse; +import dev.whatevernote.be.login.Login; import dev.whatevernote.be.service.ContentService; import dev.whatevernote.be.service.dto.request.ContentRequestDto; import dev.whatevernote.be.service.dto.response.ContentResponseDto; @@ -32,34 +33,36 @@ public ContentController(ContentService contentService) { } @PostMapping - public BaseResponse create(@RequestBody final ContentRequestDto contentRequestDto, @PathVariable final Long cardId) { - ContentResponseDto contentResponseDto = contentService.create(contentRequestDto, cardId); + public BaseResponse create(@RequestBody final ContentRequestDto contentRequestDto, + @PathVariable final Integer noteId, @PathVariable final Long cardId, @Login final Long memberId) { + ContentResponseDto contentResponseDto = contentService.create(contentRequestDto, noteId, cardId, memberId); return new BaseResponse<>(CONTENT_CREATE_SUCCESS, contentResponseDto); } @GetMapping - public BaseResponse findAll(final Pageable pageable, @PathVariable final Long cardId) { - ContentResponseDtos contentResponseDtos = contentService.findAll(pageable, cardId); + public BaseResponse findAll(final Pageable pageable, @PathVariable final Integer noteId, + @PathVariable final Long cardId, @Login final Long memberId) { + ContentResponseDtos contentResponseDtos = contentService.findAll(pageable, noteId, cardId, memberId); return new BaseResponse<>(CONTENT_RETRIEVE_ALL_SUCCESS, contentResponseDtos); } @GetMapping("/{contentId}") - public BaseResponse findById(@PathVariable final Long contentId) { - ContentResponseDto contentResponseDto = contentService.findById(contentId); + public BaseResponse findById(@PathVariable final Integer noteId, + @PathVariable final Long contentId, @Login final Long memberId) { + ContentResponseDto contentResponseDto = contentService.findById(noteId, contentId, memberId); return new BaseResponse<>(CONTENT_RETRIEVE_DETAIL_SUCCESS, contentResponseDto); } @PutMapping("/{contentId}") - public BaseResponse update(@PathVariable final Long cardId, - @PathVariable final Long contentId, - @RequestBody final ContentRequestDto contentRequestDto) { - ContentResponseDto contentResponseDto = contentService.update(cardId, contentId, contentRequestDto); + public BaseResponse update(@PathVariable final Integer noteId, @PathVariable final Long cardId, + @PathVariable final Long contentId, @RequestBody final ContentRequestDto contentRequestDto, @Login final Long memberId) { + ContentResponseDto contentResponseDto = contentService.update(noteId, cardId, contentId, contentRequestDto, memberId); return new BaseResponse<>(CONTENT_MODIFY_SUCCESS, contentResponseDto); } @DeleteMapping("{contentId}") - public BaseResponse delete(@PathVariable final Long contentId) { - contentService.delete(contentId); + public BaseResponse delete(@PathVariable final Integer noteId, @PathVariable final Long contentId, @Login final Long memberId) { + contentService.delete(noteId, contentId, memberId); return new BaseResponse<>(CONTENT_REMOVE_SUCCESS, null); } diff --git a/BE/src/main/java/dev/whatevernote/be/web/controller/NoteController.java b/BE/src/main/java/dev/whatevernote/be/web/controller/NoteController.java index 1ab821a..84fa814 100644 --- a/BE/src/main/java/dev/whatevernote/be/web/controller/NoteController.java +++ b/BE/src/main/java/dev/whatevernote/be/web/controller/NoteController.java @@ -7,6 +7,7 @@ import static dev.whatevernote.be.common.ResponseCodeAndMessages.NOTE_RETRIEVE_DETAIL_SUCCESS; import dev.whatevernote.be.common.BaseResponse; +import dev.whatevernote.be.login.Login; import dev.whatevernote.be.service.NoteService; import dev.whatevernote.be.service.dto.request.NoteRequestDto; import dev.whatevernote.be.service.dto.response.NoteResponseDto; @@ -32,29 +33,29 @@ public NoteController(final NoteService noteService) { } @GetMapping("/{noteId}") - public BaseResponse findById(@PathVariable final Integer noteId) { - return new BaseResponse<>(NOTE_RETRIEVE_DETAIL_SUCCESS, noteService.findById(noteId)); + public BaseResponse findById(@PathVariable final Integer noteId, @Login final Long memberId) { + return new BaseResponse<>(NOTE_RETRIEVE_DETAIL_SUCCESS, noteService.findById(noteId, memberId)); } @GetMapping - public BaseResponse findAll(final Pageable pageable) { - return new BaseResponse<>(NOTE_RETRIEVE_ALL_SUCCESS, noteService.findAll(pageable)); + public BaseResponse findAll(@Login final Long memberId, final Pageable pageable) { + return new BaseResponse<>(NOTE_RETRIEVE_ALL_SUCCESS, noteService.findAll(memberId, pageable)); } @PostMapping - public BaseResponse create(@RequestBody final NoteRequestDto noteRequestDto) { - return new BaseResponse<>(NOTE_CREATE_SUCCESS, noteService.create(noteRequestDto)); + public BaseResponse create(@RequestBody final NoteRequestDto noteRequestDto, @Login final Long memberId) { + return new BaseResponse<>(NOTE_CREATE_SUCCESS, noteService.create(noteRequestDto, memberId)); } @PutMapping("/{noteId}") public BaseResponse update(@PathVariable final Integer noteId, - @RequestBody final NoteRequestDto noteRequestDto) { - return new BaseResponse<>(NOTE_MODIFY_SUCCESS, noteService.update(noteId, noteRequestDto)); + @RequestBody final NoteRequestDto noteRequestDto, @Login final Long memberId) { + return new BaseResponse<>(NOTE_MODIFY_SUCCESS, noteService.update(noteId, noteRequestDto, memberId)); } @DeleteMapping("/{noteId}") - public BaseResponse delete(@PathVariable final Integer noteId) { - noteService.delete(noteId); + public BaseResponse delete(@PathVariable final Integer noteId, @Login final Long memberId) { + noteService.delete(noteId, memberId); return new BaseResponse<>(NOTE_REMOVE_SUCCESS, null); } diff --git a/BE/src/main/resources/application.yml b/BE/src/main/resources/application.yml deleted file mode 100644 index c7f9d6e..0000000 --- a/BE/src/main/resources/application.yml +++ /dev/null @@ -1,3 +0,0 @@ -spring: - profiles: - active: ${profile} diff --git a/BE/src/main/resources/sql/data.sql b/BE/src/main/resources/sql/data.sql index e62b9f3..00b0a57 100644 --- a/BE/src/main/resources/sql/data.sql +++ b/BE/src/main/resources/sql/data.sql @@ -1,14 +1,19 @@ SET AUTOCOMMIT = 0; +-- MEMBER +INSERT INTO member(email, nickname, profile_image, unique_id) +VALUES ('hgd1234@naver.com', '홍길동', 'https://dummy-image.co.kr', '12-324-523-122-11'); + + -- NOTE -INSERT INTO note(created_at, deleted, updated_at, note_order, title) -VALUES ('2022-06-04 00:00:01', false, '2022-06-04 00:00:01', 1000, '첫번째 노트'); +INSERT INTO note(created_at, deleted, updated_at, note_order, title, member_id) +VALUES ('2022-06-04 00:00:01', false, '2022-06-04 00:00:01', 1000, '첫번째 노트', 1); -INSERT INTO note(created_at, deleted, updated_at, note_order, title) -VALUES ('2022-06-05 00:00:01', false, '2022-06-05 00:00:01', 2000, '두번째 노트'); +INSERT INTO note(created_at, deleted, updated_at, note_order, title, member_id) +VALUES ('2022-06-05 00:00:01', false, '2022-06-05 00:00:01', 2000, '두번째 노트', 1); -INSERT INTO note(created_at, deleted, updated_at, note_order, title) -VALUES ('2022-06-06 00:00:01', false, '2022-06-06 00:00:01', 3000, '세번째 노트'); +INSERT INTO note(created_at, deleted, updated_at, note_order, title, member_id) +VALUES ('2022-06-06 00:00:01', false, '2022-06-06 00:00:01', 3000, '세번째 노트', 1); -- CARD INSERT INTO card(created_at, deleted, updated_at, card_order, title, note_id) diff --git a/BE/src/main/resources/static/docs/api.html b/BE/src/main/resources/static/docs/api.html index 175f17f..675640b 100644 --- a/BE/src/main/resources/static/docs/api.html +++ b/BE/src/main/resources/static/docs/api.html @@ -456,19 +456,19 @@

    Spring REST Docs

  • 단어 카드(Card)
  • 카드 내용(Content)
  • @@ -487,6 +487,7 @@

    HTTP request
    GET /api/note/1 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkyfQ._MIgQjmcFMWtDfpbDl63PZy8WLCjvh4TpweEnsXkrms
     Accept: application/json
     Host: localhost:8080

    @@ -591,6 +592,7 @@

    HTTP request
    GET /api/note?page=0&size=5 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkyfQ._MIgQjmcFMWtDfpbDl63PZy8WLCjvh4TpweEnsXkrms
     Accept: application/json
     Host: localhost:8080

    @@ -720,6 +722,7 @@

    HTTP request
    POST /api/note HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkyfQ._MIgQjmcFMWtDfpbDl63PZy8WLCjvh4TpweEnsXkrms
     Content-Length: 47
     Host: localhost:8080
     
    @@ -822,6 +825,7 @@ 

    HTTP request
    PUT /api/note/1 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkyfQ._MIgQjmcFMWtDfpbDl63PZy8WLCjvh4TpweEnsXkrms
     Content-Length: 33
     Host: localhost:8080
     
    @@ -983,6 +987,7 @@ 

    HTTP request
    DELETE /api/note/1 HTTP/1.1
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkyfQ._MIgQjmcFMWtDfpbDl63PZy8WLCjvh4TpweEnsXkrms
     Host: localhost:8080

    @@ -1033,13 +1038,14 @@

    단어

    -

    1. [GET] 단어 단일 조회

    +

    1. [GET] 단어 단일 조회

    HTTP request

    GET /api/note/1/card/1 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkxfQ.3nK4Y6wflPZ7Mggsb8CsTiFuzcTdIC9PSKZnJKAi-ic
     Accept: application/json
     Host: localhost:8080
    @@ -1185,13 +1191,14 @@

    Response

    -

    2. [GET] 단어 모두 조회

    +

    2. [GET] 단어 모두 조회

    HTTP request

    GET /api/note/1/card?page=0&size=5 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkxfQ.3nK4Y6wflPZ7Mggsb8CsTiFuzcTdIC9PSKZnJKAi-ic
     Accept: application/json
     Host: localhost:8080
    @@ -1351,6 +1358,7 @@

    HTTP request
    POST /api/note/1/card HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkxfQ.3nK4Y6wflPZ7Mggsb8CsTiFuzcTdIC9PSKZnJKAi-ic
     Content-Length: 47
     Host: localhost:8080
     
    @@ -1490,13 +1498,14 @@ 

    Response

    -

    4. [PUT] 단어 카드 수정

    +

    4. [PUT] 단어 카드 수정

    HTTP request

    PUT /api/note/1/card/1 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkxfQ.3nK4Y6wflPZ7Mggsb8CsTiFuzcTdIC9PSKZnJKAi-ic
     Content-Length: 33
     Host: localhost:8080
     
    @@ -1640,12 +1649,13 @@ 

    Response

    -

    5. [DELETE] 단어 카드 삭제

    +

    5. [DELETE] 단어 카드 삭제

    HTTP request

    DELETE /api/note/1/card/1 HTTP/1.1
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkxfQ.3nK4Y6wflPZ7Mggsb8CsTiFuzcTdIC9PSKZnJKAi-ic
     Host: localhost:8080
    @@ -1738,13 +1748,14 @@


    -

    1. [GET] 내용 전체 조회

    +

    1. [GET] 내용 전체 조회

    HTTP request

    GET /api/note/1/card/1/content?page=0&size=5 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkxfQ.3nK4Y6wflPZ7Mggsb8CsTiFuzcTdIC9PSKZnJKAi-ic
     Accept: application/json
     Host: localhost:8080
    @@ -1884,13 +1895,14 @@

    Response

    -

    2. [POST] 내용 생성

    +

    2. [POST] 내용 생성

    HTTP request

    POST /api/note/1/card/1/content HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkxfQ.3nK4Y6wflPZ7Mggsb8CsTiFuzcTdIC9PSKZnJKAi-ic
     Content-Length: 71
     Host: localhost:8080
     
    @@ -2046,13 +2058,14 @@ 

    Response

    -

    3. [PUT] 내용 수정

    +

    3. [PUT] 내용 수정

    HTTP request

    PUT /api/note/1/card/1/content/1 HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkxfQ.3nK4Y6wflPZ7Mggsb8CsTiFuzcTdIC9PSKZnJKAi-ic
     Content-Length: 70
     Host: localhost:8080
     
    @@ -2212,12 +2225,13 @@ 

    Response

    -

    4. [DELETE] 내용 삭제

    +

    4. [DELETE] 내용 삭제

    HTTP request

    DELETE /api/note/1/card/1/content/1 HTTP/1.1
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3aGF0ZXZlci1ub3RlLXNlcnZlciIsInN1YiI6InByb2plY3QtdGVzdC1hY2Nlc3MtdG9rZW4iLCJhdWQiOiIxIiwiZXhwIjoxNjc4MjAyOTkxfQ.3nK4Y6wflPZ7Mggsb8CsTiFuzcTdIC9PSKZnJKAi-ic
     Host: localhost:8080
    @@ -2313,7 +2327,7 @@

    Respons diff --git a/BE/src/test/java/dev/whatevernote/be/service/InitIntegrationTest.java b/BE/src/test/java/dev/whatevernote/be/service/InitIntegrationTest.java index 493d8cc..d31ef06 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/InitIntegrationTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/InitIntegrationTest.java @@ -10,6 +10,8 @@ @ActiveProfiles("test") public abstract class InitIntegrationTest { + protected static final Long MEMBER_ID = 1L; + protected static final int FIRST_NOTE_ID = 1; protected static final int DEFAULT_RANGE = 1_000; @Autowired private DataBaseConfigurator testData; diff --git a/BE/src/test/java/dev/whatevernote/be/service/card/CardCreateTest.java b/BE/src/test/java/dev/whatevernote/be/service/card/CardCreateTest.java index 0e5ea6d..9e9a00f 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/card/CardCreateTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/card/CardCreateTest.java @@ -8,10 +8,8 @@ import dev.whatevernote.be.service.dto.request.CardRequestDto; import dev.whatevernote.be.service.dto.request.NoteRequestDto; import dev.whatevernote.be.service.dto.response.CardResponseDto; -import dev.whatevernote.be.service.dto.response.CardResponseDtos; import dev.whatevernote.be.service.dto.response.NoteResponseDto; import java.util.List; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -21,6 +19,7 @@ @DisplayName("통합 테스트 : Card 생성") public class CardCreateTest extends InitIntegrationTest { + private static final long NEW_MEMBER_ID = 1L; private static final int TEMP_NOTE_ID = 1; private static final int NUMBER_OF_NOTE = 3; @@ -43,13 +42,14 @@ class CardNormalCreateTest { void not_existing_card_with_request_seq_is_zero() { //given noteService.create( - new NoteRequestDto(null, "새로 생성한 노트") + new NoteRequestDto(null, "새로 생성한 노트"), MEMBER_ID ); - NoteResponseDto note = noteService.findById(NUMBER_OF_NOTE + 1); + NoteResponseDto note = noteService.findById(NUMBER_OF_NOTE + 1, NEW_MEMBER_ID); CardRequestDto cardRequestDto = new CardRequestDto(0L, "나만의 카드"); //when - CardResponseDto cardResponseDto = cardService.create(cardRequestDto, note.getId()); + CardResponseDto cardResponseDto = cardService.create(cardRequestDto, note.getId(), + MEMBER_ID); //then assertThat(cardResponseDto.getSeq()).isEqualTo(DEFAULT_RANGE); @@ -60,13 +60,14 @@ void not_existing_card_with_request_seq_is_zero() { void not_existing_card_with_request_seq_is_not_zero() { //given noteService.create( - new NoteRequestDto(null, "새로 생성한 노트") + new NoteRequestDto(null, "새로 생성한 노트"), MEMBER_ID ); - NoteResponseDto note = noteService.findById(NUMBER_OF_NOTE + 1); + NoteResponseDto note = noteService.findById(NUMBER_OF_NOTE + 1, NEW_MEMBER_ID); CardRequestDto cardRequestDto = new CardRequestDto(10L, "나만의 카드"); //when - CardResponseDto cardResponseDto = cardService.create(cardRequestDto, note.getId()); + CardResponseDto cardResponseDto = cardService.create(cardRequestDto, note.getId(), + MEMBER_ID); //then assertThat(cardResponseDto.getSeq()).isEqualTo(DEFAULT_RANGE); @@ -77,17 +78,18 @@ void not_existing_card_with_request_seq_is_not_zero() { void existing_card_with_over_request_seq() { //given noteService.create( - new NoteRequestDto(null, "새로 생성한 노트") + new NoteRequestDto(null, "새로 생성한 노트"), NEW_MEMBER_ID ); - NoteResponseDto note = noteService.findById(NUMBER_OF_NOTE + 1); - cardService.create(new CardRequestDto(0L, "나만의 카드1"), note.getId()); - cardService.create(new CardRequestDto(1L, "나만의 카드2"), note.getId()); - cardService.create(new CardRequestDto(2L, "나만의 카드3"), note.getId()); + NoteResponseDto note = noteService.findById(NUMBER_OF_NOTE + 1, NEW_MEMBER_ID); + cardService.create(new CardRequestDto(0L, "나만의 카드1"), note.getId(), NEW_MEMBER_ID); + cardService.create(new CardRequestDto(1L, "나만의 카드2"), note.getId(), NEW_MEMBER_ID); + cardService.create(new CardRequestDto(2L, "나만의 카드3"), note.getId(), NEW_MEMBER_ID); CardRequestDto cardRequestDto = new CardRequestDto(10L, "나만의 카드"); int numberOfNotes = 4; //when - CardResponseDto cardResponseDto = cardService.create(cardRequestDto, note.getId()); + CardResponseDto cardResponseDto = cardService.create(cardRequestDto, note.getId(), + MEMBER_ID); //then assertThat(cardResponseDto.getSeq()).isEqualTo(DEFAULT_RANGE*numberOfNotes); @@ -106,10 +108,12 @@ class NoteSeqOmissionCreateTest{ void seq_null_and_existing_card(){ //given CardRequestDto cardRequestDto = new CardRequestDto(null, "나만의 단어장"); - List cards = cardService.findAll(Pageable.unpaged(), TEMP_NOTE_ID).getCards(); + List cards = cardService.findAll(Pageable.unpaged(), TEMP_NOTE_ID, + MEMBER_ID).getCards(); //when - CardResponseDto cardResponseDto = cardService.create(cardRequestDto, TEMP_NOTE_ID); + CardResponseDto cardResponseDto = cardService.create(cardRequestDto, TEMP_NOTE_ID, + MEMBER_ID); //then assertThat(cardResponseDto.getSeq()).isEqualTo(cards.get(0).getSeq()/2); @@ -121,13 +125,14 @@ void seq_null_and_existing_card(){ void seq_null_and_not_existing_card(){ //given noteService.create( - new NoteRequestDto(null, "새로 생성한 노트") + new NoteRequestDto(null, "새로 생성한 노트"), NEW_MEMBER_ID ); - NoteResponseDto note = noteService.findById(NUMBER_OF_NOTE + 1); + NoteResponseDto note = noteService.findById(NUMBER_OF_NOTE + 1, NEW_MEMBER_ID); CardRequestDto cardRequestDto = new CardRequestDto(null, "나만의 단어장"); //when - CardResponseDto cardResponseDto = cardService.create(cardRequestDto, note.getId()); + CardResponseDto cardResponseDto = cardService.create(cardRequestDto, note.getId(), + NEW_MEMBER_ID); //then assertThat(cardResponseDto.getSeq()).isEqualTo(DEFAULT_RANGE); @@ -147,7 +152,8 @@ void null_title(){ CardRequestDto cardRequestDto = new CardRequestDto(null, null); //when - CardResponseDto cardResponseDto = cardService.create(cardRequestDto, TEMP_NOTE_ID); + CardResponseDto cardResponseDto = cardService.create(cardRequestDto, TEMP_NOTE_ID, + MEMBER_ID); //then assertThat(cardResponseDto.getTitle()).isNull(); diff --git a/BE/src/test/java/dev/whatevernote/be/service/card/CardDeleteTest.java b/BE/src/test/java/dev/whatevernote/be/service/card/CardDeleteTest.java index a5351af..991e358 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/card/CardDeleteTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/card/CardDeleteTest.java @@ -44,19 +44,19 @@ class NormalDeleteTest { @Test void soft_delete_card(){ //given - List cards = cardRepository.findAllByNoteId(NOTE_ID_1); + List cards = cardRepository.findAllByNoteIdOrderBySeq(NOTE_ID_1); int numberOfCard = cards.size(); long deleteCardId = 2; //when - cardService.delete(deleteCardId); + cardService.delete(NOTE_ID_1, deleteCardId, MEMBER_ID); Optional card = cardRepository.findById(deleteCardId); - List afterDelete = cardRepository.findAllByNoteId(NOTE_ID_1); + List afterDelete = cardRepository.findAllByNoteIdOrderBySeq(NOTE_ID_1); //then assertThat(card).isEmpty(); assertThat(afterDelete).hasSize(numberOfCard-1); - assertThatThrownBy(() -> cardService.findById(NOTE_ID_1, deleteCardId)) + assertThatThrownBy(() -> cardService.findById(NOTE_ID_1, deleteCardId, MEMBER_ID)) .isInstanceOf(NotFoundCardException.class) .hasMessageContaining(ErrorCodeAndMessages.E404_NOT_FOUND_CARD.getMessage()); @@ -67,21 +67,21 @@ void soft_delete_card(){ void soft_delete_card_and_content(){ //given long deleteCardId = 2; - List cards = cardRepository.findAllByNoteId(NOTE_ID_1); + List cards = cardRepository.findAllByNoteIdOrderBySeq(NOTE_ID_1); int numberOfCard = cards.size(); //when - cardService.delete(deleteCardId); + cardService.delete(NOTE_ID_1, deleteCardId, MEMBER_ID); Optional card = cardRepository.findById(deleteCardId); - List afterDelete = cardRepository.findAllByNoteId(NOTE_ID_1); - List afterDeleteContents = contentRepository.findAllByCardId(deleteCardId); + List afterDelete = cardRepository.findAllByNoteIdOrderBySeq(NOTE_ID_1); + List afterDeleteContents = contentRepository.findAllByCardIdOrderBySeqAsc(deleteCardId); //then // card assertThat(card).isEmpty(); assertThat(afterDelete).hasSize(numberOfCard-1); - assertThatThrownBy(() -> cardService.findById(NOTE_ID_1, deleteCardId)) + assertThatThrownBy(() -> cardService.findById(NOTE_ID_1, deleteCardId, MEMBER_ID)) .isInstanceOf(NotFoundCardException.class) .hasMessageContaining(ErrorCodeAndMessages.E404_NOT_FOUND_CARD.getMessage()); diff --git a/BE/src/test/java/dev/whatevernote/be/service/card/CardFindTest.java b/BE/src/test/java/dev/whatevernote/be/service/card/CardFindTest.java index 957647f..621fb86 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/card/CardFindTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/card/CardFindTest.java @@ -5,36 +5,29 @@ import dev.whatevernote.be.repository.CardRepository; import dev.whatevernote.be.service.CardService; import dev.whatevernote.be.service.ContentService; +import dev.whatevernote.be.service.InitIntegrationTest; import dev.whatevernote.be.service.NoteService; import dev.whatevernote.be.service.domain.Card; -import dev.whatevernote.be.service.dto.request.CardRequestDto; -import dev.whatevernote.be.service.dto.request.ContentRequestDto; -import dev.whatevernote.be.service.dto.request.NoteRequestDto; import dev.whatevernote.be.service.dto.response.CardDetailResponseDto; import dev.whatevernote.be.service.dto.response.CardResponseDto; import dev.whatevernote.be.service.dto.response.CardResponseDtos; import dev.whatevernote.be.service.dto.response.ContentResponseDto; import java.util.List; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.PageRequest; -import org.springframework.test.context.jdbc.Sql; @DisplayName("통합 테스트 : Card & Content 조회") -@Sql("/truncate.sql") -@SpringBootTest -class CardFindTest { +class CardFindTest extends InitIntegrationTest { + + private static final int NUMBER_OF_CARD = 3; + private static final int NUMBER_OF_CONTENT = 5; - private static final int NUMBER_OF_CARD = 20; - private static final int FIRST_NOTE_ID = 1; - private static final int SECOND_NOTE_ID = 2; private static final int PAGE_NUMBER = 0; private static final int PAGE_SIZE = 5; - private static final String CONTENT_INFO = "contentInfo-"; + private static final String CONTENT_INFO = "contentINFO-"; @Autowired private CardService cardService; @@ -48,41 +41,6 @@ class CardFindTest { @Autowired private CardRepository cardRepository; - @BeforeEach - void init() { - createNotesAndCardsAndContents(NUMBER_OF_CARD); - } - - private void createNotesAndCardsAndContents(int numberOfCard){ - - for (int i = 0; i < 2; i++) { - noteService.create(new NoteRequestDto(i, "note-" + (i + 1))); - } - long firstCardId = 0L; - for (int i = 0; i < numberOfCard; i++) { - CardRequestDto cardRequestDto = new CardRequestDto((long) i, "card-" + (i + 1)); - CardResponseDto cardResponseDto; - ContentRequestDto contentRequestDto; - if (i < (numberOfCard / 2)) { - cardResponseDto = cardService.create(cardRequestDto, FIRST_NOTE_ID); - contentRequestDto = new ContentRequestDto(CONTENT_INFO + (i), (long) i, Boolean.FALSE); - } else { - cardResponseDto = cardService.create(cardRequestDto, SECOND_NOTE_ID); - contentRequestDto = new ContentRequestDto(CONTENT_INFO + (i), (long) i, Boolean.TRUE); - } - - if (i == 0) { - firstCardId = cardResponseDto.getId(); - } - - contentService.create(contentRequestDto, firstCardId); - if (i > 0) { - contentService.create(contentRequestDto, firstCardId+1); - } - - } - } - @Nested @DisplayName("카드를 단건 조회할 때") class FindOneTest { @@ -95,12 +53,12 @@ class NormalFindOneTest { @Test void normal_find_one(){ //given - List cards = cardRepository.findAllByOrderBySeq(); + List cards = cardRepository.findAllByNoteIdOrderBySeqAsc(FIRST_NOTE_ID); int seq = 0; long tmpCardId = cards.get(seq).getId(); //when - CardDetailResponseDto cardDetailResponseDto = cardService.findById(FIRST_NOTE_ID, tmpCardId); + CardDetailResponseDto cardDetailResponseDto = cardService.findById(FIRST_NOTE_ID, tmpCardId, MEMBER_ID); List contents = cardDetailResponseDto.getContents(); @@ -108,10 +66,9 @@ void normal_find_one(){ //card assertThat(cardDetailResponseDto.getCardId()).isEqualTo(tmpCardId); assertThat(cardDetailResponseDto.getNoteId()).isEqualTo(FIRST_NOTE_ID); - assertThat(cardDetailResponseDto.getCardTitle()).isEqualTo("card-" + (seq + 1)); + assertThat(cardDetailResponseDto.getCardTitle()).isEqualTo("cardTitle-" + (seq + 1)); //contents - assertThat(cardDetailResponseDto.getContents()).hasSize(NUMBER_OF_CARD); assertThat(contents.get(0).getCardId()).isEqualTo(tmpCardId); assertThat(contents.get(0).getInfo()).isEqualTo(CONTENT_INFO+"0"); assertThat(contents.get(0).getIsImage()).isFalse(); @@ -133,7 +90,7 @@ class NormalFindAllTest { void normal_find_all() { //given PageRequest defaultPageRequest = PageRequest.of(PAGE_NUMBER, PAGE_SIZE); - CardResponseDtos cardResponseDtos = cardService.findAll(defaultPageRequest, FIRST_NOTE_ID); + CardResponseDtos cardResponseDtos = cardService.findAll(defaultPageRequest, FIRST_NOTE_ID, MEMBER_ID); //when List cards = cardResponseDtos.getCards(); @@ -142,10 +99,10 @@ void normal_find_all() { long preSeq = 0L; //then - assertThat(cards).hasSize(PAGE_SIZE); + assertThat(cards).hasSize(NUMBER_OF_CARD % PAGE_SIZE); assertThat(pageNumber).isEqualTo(PAGE_NUMBER); - assertThat(hasNext).isTrue(); - for (int i = 0; i < PAGE_SIZE; i++) { + assertThat(hasNext).isFalse(); + for (int i = 0; i < NUMBER_OF_CARD % PAGE_SIZE; i++) { CardResponseDto card = cards.get(i); assertThat(card.getNoteId()).isEqualTo(FIRST_NOTE_ID); assertThat(card.getSeq()).isGreaterThan(preSeq); @@ -172,11 +129,12 @@ void normal_find_one_content(){ long tmpContentId = 1L; //when - ContentResponseDto contentResponseDto = contentService.findById(tmpContentId); + ContentResponseDto contentResponseDto = contentService + .findById(FIRST_NOTE_ID, tmpContentId, MEMBER_ID); //then assertThat(contentResponseDto.getId()).isEqualTo(tmpContentId); - assertThat(contentResponseDto.getInfo()).isEqualTo("contentInfo-0"); + assertThat(contentResponseDto.getInfo()).isEqualTo("contentINFO-0"); assertThat(contentResponseDto.getSeq()).isEqualTo(1000); assertThat(contentResponseDto.getIsImage()).isFalse(); assertThat(contentResponseDto.getCardId()).isEqualTo(tmpCardId); diff --git a/BE/src/test/java/dev/whatevernote/be/service/card/CardUpdateTest.java b/BE/src/test/java/dev/whatevernote/be/service/card/CardUpdateTest.java index 79f0e40..12efe0d 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/card/CardUpdateTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/card/CardUpdateTest.java @@ -38,11 +38,12 @@ class NormalTitleUpdateTest { @Test void card_title_update() { //given - CardDetailResponseDto card = cardService.findById(NOTE_ID_1, CARD_ID_1); + CardDetailResponseDto card = cardService.findById(NOTE_ID_1, CARD_ID_1, MEMBER_ID); CardRequestDto cardRequestDto = new CardRequestDto(null, "제목 바꾼 카드"); //when - CardResponseDto updatedCardResponse = cardService.update(NOTE_ID_1, CARD_ID_1, cardRequestDto); + CardResponseDto updatedCardResponse = cardService.update(NOTE_ID_1, CARD_ID_1, cardRequestDto, + MEMBER_ID); //then assertThat(updatedCardResponse.getTitle()).isEqualTo("제목 바꾼 카드"); @@ -60,11 +61,12 @@ class NormalSeqUpdateTest { @Test void card_seq_update_to_first() { //given - CardDetailResponseDto card = cardService.findById(NOTE_ID_1, CARD_ID_3); + CardDetailResponseDto card = cardService.findById(NOTE_ID_1, CARD_ID_3, MEMBER_ID); CardRequestDto cardRequestDto = new CardRequestDto(0L, null); //when - CardResponseDto updatedCardResponse = cardService.update(NOTE_ID_1, CARD_ID_3, cardRequestDto); + CardResponseDto updatedCardResponse = cardService.update(NOTE_ID_1, CARD_ID_3, cardRequestDto, + MEMBER_ID); //then assertThat(updatedCardResponse.getTitle()).isEqualTo(card.getCardTitle()); @@ -77,11 +79,12 @@ void card_seq_update_to_first() { @Test void card_seq_update(){ //given - CardDetailResponseDto card = cardService.findById(NOTE_ID_1, CARD_ID_3); + CardDetailResponseDto card = cardService.findById(NOTE_ID_1, CARD_ID_3, MEMBER_ID); CardRequestDto cardRequestDto = new CardRequestDto(1L, null); //when - CardResponseDto updatedCardResponse = cardService.update(NOTE_ID_1, CARD_ID_3, cardRequestDto); + CardResponseDto updatedCardResponse = cardService.update(NOTE_ID_1, CARD_ID_3, cardRequestDto, + MEMBER_ID); //then assertThat(updatedCardResponse.getTitle()).isEqualTo(card.getCardTitle()); @@ -94,11 +97,12 @@ void card_seq_update(){ @Test void card_seq_update_to_last(){ //given - CardDetailResponseDto card = cardService.findById(NOTE_ID_1, CARD_ID_1); + CardDetailResponseDto card = cardService.findById(NOTE_ID_1, CARD_ID_1, MEMBER_ID); CardRequestDto cardRequestDto = new CardRequestDto(100L, null); //when - CardResponseDto updatedCardResponse = cardService.update(NOTE_ID_1, CARD_ID_1, cardRequestDto); + CardResponseDto updatedCardResponse = cardService.update(NOTE_ID_1, CARD_ID_1, cardRequestDto, + MEMBER_ID); //then assertThat(updatedCardResponse.getTitle()).isEqualTo(card.getCardTitle()); @@ -111,11 +115,12 @@ void card_seq_update_to_last(){ @Test void card_seq_update_to_same_seq(){ //given - CardDetailResponseDto card = cardService.findById(NOTE_ID_1, CARD_ID_1); + CardDetailResponseDto card = cardService.findById(NOTE_ID_1, CARD_ID_1, MEMBER_ID); CardRequestDto cardRequestDto = new CardRequestDto(1L, null); //when - CardResponseDto updatedCardResponse = cardService.update(NOTE_ID_1, CARD_ID_1, cardRequestDto); + CardResponseDto updatedCardResponse = cardService.update(NOTE_ID_1, CARD_ID_1, cardRequestDto, + MEMBER_ID); //then assertThat(updatedCardResponse.getTitle()).isEqualTo(card.getCardTitle()); diff --git a/BE/src/test/java/dev/whatevernote/be/service/content/ContentCreateTest.java b/BE/src/test/java/dev/whatevernote/be/service/content/ContentCreateTest.java index f4c02c8..0b1452e 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/content/ContentCreateTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/content/ContentCreateTest.java @@ -17,8 +17,6 @@ @DisplayName("통합 테스트 : Content 생성") class ContentCreateTest extends InitIntegrationTest { - private static final int NOTE_ID = 1; - @Autowired private CardService cardService; @Autowired @@ -37,13 +35,14 @@ class ContentNormalCreateTest { void not_existing_content_with_request_seq_is_zero(){ //given CardResponseDto cardResponse = cardService.create( - new CardRequestDto(null, "컨텐트 생성 테스트를 위한 카드"), NOTE_ID); + new CardRequestDto(null, "컨텐트 생성 테스트를 위한 카드"), FIRST_NOTE_ID, MEMBER_ID); Long newCardId = cardResponse.getId(); //when ContentResponseDto contentResponse = contentService.create( new ContentRequestDto("새로운 컨텐트", 0L, Boolean.FALSE), - newCardId + FIRST_NOTE_ID, newCardId, + MEMBER_ID ); //then @@ -58,13 +57,14 @@ void not_existing_content_with_request_seq_is_zero(){ void not_existing_content_with_request_seq_is_not_zero(){ //given CardResponseDto cardResponse = cardService.create( - new CardRequestDto(null, "컨텐트 생성 테스트를 위한 카드"), NOTE_ID); + new CardRequestDto(null, "컨텐트 생성 테스트를 위한 카드"), FIRST_NOTE_ID, MEMBER_ID); Long newCardId = cardResponse.getId(); //when ContentResponseDto contentResponse = contentService.create( new ContentRequestDto("새로운 컨텐트", 10L, Boolean.FALSE), - newCardId + FIRST_NOTE_ID, newCardId, + MEMBER_ID ); //then @@ -79,18 +79,20 @@ void not_existing_content_with_request_seq_is_not_zero(){ void existing_content_with_over_request_seq(){ //given CardResponseDto cardResponse = cardService.create( - new CardRequestDto(null, "컨텐트 생성 테스트를 위한 카드"), NOTE_ID); + new CardRequestDto(null, "컨텐트 생성 테스트를 위한 카드"), FIRST_NOTE_ID, MEMBER_ID); Long newCardId = cardResponse.getId(); //when ContentResponseDto contentResponse = contentService.create( new ContentRequestDto("새로운 컨텐트", 0L, Boolean.FALSE), - newCardId + FIRST_NOTE_ID, newCardId, + MEMBER_ID ); ContentResponseDto contentResponse2 = contentService.create( new ContentRequestDto("새로운 컨텐트2", 10L, Boolean.FALSE), - newCardId + FIRST_NOTE_ID, newCardId, + MEMBER_ID ); //then @@ -110,18 +112,20 @@ class ContentSeqOmissionCreateTest { void seq_null_and_existing_content(){ //given CardResponseDto cardResponse = cardService.create( - new CardRequestDto(null, "컨텐트 생성 테스트를 위한 카드"), NOTE_ID); + new CardRequestDto(null, "컨텐트 생성 테스트를 위한 카드"), FIRST_NOTE_ID, MEMBER_ID); Long newCardId = cardResponse.getId(); //when ContentResponseDto contentResponse = contentService.create( new ContentRequestDto("새로운 컨텐트", 0L, Boolean.FALSE), - newCardId + FIRST_NOTE_ID, newCardId, + MEMBER_ID ); ContentResponseDto contentResponse2 = contentService.create( new ContentRequestDto("새로운 컨텐트2", null, Boolean.FALSE), - newCardId + FIRST_NOTE_ID, newCardId, + MEMBER_ID ); //then @@ -136,13 +140,14 @@ void seq_null_and_existing_content(){ void seq_null_and_not_existing_content(){ //given CardResponseDto cardResponse = cardService.create( - new CardRequestDto(null, "컨텐트 생성 테스트를 위한 카드"), NOTE_ID); + new CardRequestDto(null, "컨텐트 생성 테스트를 위한 카드"), FIRST_NOTE_ID, MEMBER_ID); Long newCardId = cardResponse.getId(); //when ContentResponseDto contentResponse = contentService.create( new ContentRequestDto("새로운 컨텐트", null, Boolean.FALSE), - newCardId + FIRST_NOTE_ID, newCardId, + MEMBER_ID ); //then diff --git a/BE/src/test/java/dev/whatevernote/be/service/content/ContentDeleteTest.java b/BE/src/test/java/dev/whatevernote/be/service/content/ContentDeleteTest.java index 6f4e348..77a12e7 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/content/ContentDeleteTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/content/ContentDeleteTest.java @@ -40,18 +40,18 @@ class NormalDeleteTest { @Test void soft_delete_content() { //given - List contents = contentRepository.findAllByCardId(CARD_ID); + List contents = contentRepository.findAllByCardIdOrderBySeqAsc(CARD_ID); int numberOfContent = contents.size(); //when - contentService.delete(CONTENT_ID); + contentService.delete(FIRST_NOTE_ID, CONTENT_ID, MEMBER_ID); Optional content = contentRepository.findById(CONTENT_ID); - List afterDelete = contentRepository.findAllByCardId(CARD_ID); + List afterDelete = contentRepository.findAllByCardIdOrderBySeqAsc(CARD_ID); //then assertThat(content).isEmpty(); assertThat(afterDelete).hasSize(numberOfContent - 1); - assertThatThrownBy(() -> contentService.findById(CONTENT_ID)) + assertThatThrownBy(() -> contentService.findById(FIRST_NOTE_ID, CONTENT_ID, MEMBER_ID)) .isInstanceOf(NotFoundContentException.class) .hasMessageContaining(ErrorCodeAndMessages.E404_NOT_FOUND_CONTENT.getMessage()); } diff --git a/BE/src/test/java/dev/whatevernote/be/service/content/ContentFindTest.java b/BE/src/test/java/dev/whatevernote/be/service/content/ContentFindTest.java index 1f2801f..42ef816 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/content/ContentFindTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/content/ContentFindTest.java @@ -42,7 +42,9 @@ void normal_find_all(){ //given // [CARD ID = 1] CONTENTS 개수: 10개 PageRequest defaultPageRequest = PageRequest.of(PAGE_NUMBER, PAGE_SIZE); - ContentResponseDtos contentResponseDtos = contentService.findAll(defaultPageRequest, CARD_ID); + ContentResponseDtos contentResponseDtos = contentService.findAll(defaultPageRequest, + FIRST_NOTE_ID, CARD_ID, + MEMBER_ID); //when long preSeq = 0L; diff --git a/BE/src/test/java/dev/whatevernote/be/service/content/ContentUpdateTest.java b/BE/src/test/java/dev/whatevernote/be/service/content/ContentUpdateTest.java index ac60aaa..7aebcc2 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/content/ContentUpdateTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/content/ContentUpdateTest.java @@ -31,11 +31,12 @@ class NormalInfoUpdateTest { @Test void content_info_update(){ //given - ContentResponseDto content = contentService.findById(CONTENT_ID); + ContentResponseDto content = contentService.findById(FIRST_NOTE_ID, CONTENT_ID, MEMBER_ID); ContentRequestDto contentRequestDto = new ContentRequestDto("수정할 내용", null, null); //when - ContentResponseDto updatedContentResponse = contentService.update(CARD_ID, CONTENT_ID, contentRequestDto); + ContentResponseDto updatedContentResponse = contentService + .update(FIRST_NOTE_ID, CARD_ID, CONTENT_ID, contentRequestDto, MEMBER_ID); //then assertThat(updatedContentResponse.getInfo()).isEqualTo("수정할 내용"); @@ -53,11 +54,12 @@ class NormalSeqUpdateTest { @Test void content_seq_update_to_first(){ //given - ContentResponseDto content = contentService.findById(CONTENT_ID); + ContentResponseDto content = contentService.findById(FIRST_NOTE_ID, CONTENT_ID, MEMBER_ID); ContentRequestDto contentRequestDto = new ContentRequestDto(null, 0L, null); //when - ContentResponseDto updatedContentResponse = contentService.update(CARD_ID, CONTENT_ID, contentRequestDto); + ContentResponseDto updatedContentResponse = contentService + .update(FIRST_NOTE_ID, CARD_ID, CONTENT_ID, contentRequestDto, MEMBER_ID); //then assertThat(updatedContentResponse.getInfo()).isEqualTo(content.getInfo()); @@ -70,11 +72,12 @@ void content_seq_update_to_first(){ @Test void content_seq_update(){ //given - ContentResponseDto content = contentService.findById(CONTENT_ID); + ContentResponseDto content = contentService.findById(FIRST_NOTE_ID, CONTENT_ID, MEMBER_ID); ContentRequestDto contentRequestDto = new ContentRequestDto(null, 3L, null); //when - ContentResponseDto updatedContentResponse = contentService.update(CARD_ID, CONTENT_ID, contentRequestDto); + ContentResponseDto updatedContentResponse = contentService + .update(FIRST_NOTE_ID, CARD_ID, CONTENT_ID, contentRequestDto, MEMBER_ID); //then assertThat(updatedContentResponse.getInfo()).isEqualTo(content.getInfo()); @@ -87,11 +90,12 @@ void content_seq_update(){ @Test void content_seq_update_to_last(){ //given - ContentResponseDto content = contentService.findById(CONTENT_ID); + ContentResponseDto content = contentService.findById(FIRST_NOTE_ID, CONTENT_ID, MEMBER_ID); ContentRequestDto contentRequestDto = new ContentRequestDto(null, 100L, null); //when - ContentResponseDto updatedContentResponse = contentService.update(CARD_ID, CONTENT_ID, contentRequestDto); + ContentResponseDto updatedContentResponse = contentService + .update(FIRST_NOTE_ID, CARD_ID, CONTENT_ID, contentRequestDto, MEMBER_ID); //then assertThat(updatedContentResponse.getInfo()).isEqualTo(content.getInfo()); @@ -104,11 +108,12 @@ void content_seq_update_to_last(){ @Test void content_seq_update_to_same_seq(){ //given - ContentResponseDto content = contentService.findById(CONTENT_ID); + ContentResponseDto content = contentService.findById(FIRST_NOTE_ID, CONTENT_ID, MEMBER_ID); ContentRequestDto contentRequestDto = new ContentRequestDto(null, 1L, null); //when - ContentResponseDto updatedContentResponse = contentService.update(CARD_ID, CONTENT_ID, contentRequestDto); + ContentResponseDto updatedContentResponse = contentService + .update(FIRST_NOTE_ID, CARD_ID, CONTENT_ID, contentRequestDto, MEMBER_ID); //then assertThat(updatedContentResponse.getInfo()).isEqualTo(content.getInfo()); @@ -126,11 +131,12 @@ class NormalIsImageUpdateTest { @Test void content_isImage_update(){ //given - ContentResponseDto content = contentService.findById(CONTENT_ID); + ContentResponseDto content = contentService.findById(FIRST_NOTE_ID, CONTENT_ID, MEMBER_ID); ContentRequestDto contentRequestDto = new ContentRequestDto("update_image.png", null, Boolean.TRUE); //when - ContentResponseDto updatedContentResponse = contentService.update(CARD_ID, CONTENT_ID, contentRequestDto); + ContentResponseDto updatedContentResponse = contentService + .update(FIRST_NOTE_ID, CARD_ID, CONTENT_ID, contentRequestDto, MEMBER_ID); //then assertThat(updatedContentResponse.getInfo()).isEqualTo("update_image.png"); diff --git a/BE/src/test/java/dev/whatevernote/be/service/note/NoteCreateTest.java b/BE/src/test/java/dev/whatevernote/be/service/note/NoteCreateTest.java index 5e51bd8..7f16d3b 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/note/NoteCreateTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/note/NoteCreateTest.java @@ -3,22 +3,19 @@ import static org.assertj.core.api.Assertions.assertThat; import dev.whatevernote.be.repository.NoteRepository; +import dev.whatevernote.be.service.InitIntegrationTest; import dev.whatevernote.be.service.NoteService; import dev.whatevernote.be.service.dto.request.NoteRequestDto; import dev.whatevernote.be.service.dto.response.NoteResponseDto; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.jdbc.Sql; @DisplayName("통합 테스트 : Note 생성") -@Sql("/truncate.sql") -@SpringBootTest -class NoteCreateTest { +class NoteCreateTest extends InitIntegrationTest { + private static final long NEW_MEMBER_ID = 2L; private static final int DEFAULT_RANGE = 1_000; @Autowired @@ -42,7 +39,7 @@ void not_existing_note_with_request_seq_is_zero() { NoteRequestDto noteRequestDto = new NoteRequestDto(0, "나만의 단어장"); //when - NoteResponseDto noteResponseDto = noteService.create(noteRequestDto); + NoteResponseDto noteResponseDto = noteService.create(noteRequestDto, NEW_MEMBER_ID); //then assertThat(noteResponseDto.getSeq()).isEqualTo(DEFAULT_RANGE); @@ -55,7 +52,7 @@ void not_existing_note_with_request_seq_is_not_zero() { NoteRequestDto noteRequestDto = new NoteRequestDto(10, "나만의 단어장"); //when - NoteResponseDto noteResponseDto = noteService.create(noteRequestDto); + NoteResponseDto noteResponseDto = noteService.create(noteRequestDto, NEW_MEMBER_ID); //then assertThat(noteResponseDto.getSeq()).isEqualTo(DEFAULT_RANGE); @@ -65,13 +62,13 @@ void not_existing_note_with_request_seq_is_not_zero() { @DisplayName("기존에 노트가 하나라도 존재하면, 요청된 노트 순서의 다음 순서로 노트가 생성된다.") void existing_note() { //given - noteService.create(new NoteRequestDto(0, "나만의 단어장1")); - noteService.create(new NoteRequestDto(1, "나만의 단어장2")); - noteService.create(new NoteRequestDto(2, "나만의 단어장3")); + noteService.create(new NoteRequestDto(0, "나만의 단어장1"), NEW_MEMBER_ID); + noteService.create(new NoteRequestDto(1, "나만의 단어장2"), NEW_MEMBER_ID); + noteService.create(new NoteRequestDto(2, "나만의 단어장3"), NEW_MEMBER_ID); NoteRequestDto noteRequestDto = new NoteRequestDto(2, "나만의 단어장"); //when - NoteResponseDto noteResponseDto = noteService.create(noteRequestDto); + NoteResponseDto noteResponseDto = noteService.create(noteRequestDto, MEMBER_ID); //then assertThat(noteResponseDto.getSeq()).isEqualTo(2500); @@ -82,14 +79,14 @@ void existing_note() { @DisplayName("기존에 노트 개수가 요청된 노트 순서보다 클 때, 맨 마지막 순서로 노트가 생성된다.") void existing_note_with_over_request_seq() { //given - noteService.create(new NoteRequestDto(0, "나만의 단어장1")); - noteService.create(new NoteRequestDto(1, "나만의 단어장2")); - noteService.create(new NoteRequestDto(2, "나만의 단어장3")); + noteService.create(new NoteRequestDto(0, "나만의 단어장1"), NEW_MEMBER_ID); + noteService.create(new NoteRequestDto(1, "나만의 단어장2"), NEW_MEMBER_ID); + noteService.create(new NoteRequestDto(2, "나만의 단어장3"), NEW_MEMBER_ID); NoteRequestDto noteRequestDto = new NoteRequestDto(10, "나만의 단어장"); int numberOfNotes = 4; //when - NoteResponseDto noteResponseDto = noteService.create(noteRequestDto); + NoteResponseDto noteResponseDto = noteService.create(noteRequestDto, MEMBER_ID); //then assertThat(noteResponseDto.getSeq()).isEqualTo(DEFAULT_RANGE*numberOfNotes); @@ -107,13 +104,13 @@ class NoteSeqOmissionCreateTest{ @DisplayName("기존에 노트가 하나라도 존재할 때, 가장 빠른 번호의 절반으로 노트 번호가 할당하여 생성한다.") void seq_null_and_existing_note(){ //given - noteService.create(new NoteRequestDto(0, "나만의 단어장1")); - noteService.create(new NoteRequestDto(1, "나만의 단어장2")); - noteService.create(new NoteRequestDto(2, "나만의 단어장3")); + noteService.create(new NoteRequestDto(0, "나만의 단어장1"), NEW_MEMBER_ID); + noteService.create(new NoteRequestDto(1, "나만의 단어장2"), NEW_MEMBER_ID); + noteService.create(new NoteRequestDto(2, "나만의 단어장3"), NEW_MEMBER_ID); NoteRequestDto noteRequestDto = new NoteRequestDto(null, "나만의 단어장"); //when - NoteResponseDto noteResponseDto = noteService.create(noteRequestDto); + NoteResponseDto noteResponseDto = noteService.create(noteRequestDto, MEMBER_ID); //then assertThat(noteResponseDto.getSeq()).isEqualTo(500); @@ -126,7 +123,7 @@ void seq_null_and_not_existing_note(){ NoteRequestDto noteRequestDto = new NoteRequestDto(null, "나만의 단어장"); //when - NoteResponseDto noteResponseDto = noteService.create(noteRequestDto); + NoteResponseDto noteResponseDto = noteService.create(noteRequestDto, NEW_MEMBER_ID); //then assertThat(noteResponseDto.getSeq()).isEqualTo(DEFAULT_RANGE); @@ -146,7 +143,7 @@ void null_title(){ NoteRequestDto noteRequestDto = new NoteRequestDto(null, null); //when - NoteResponseDto noteResponseDto = noteService.create(noteRequestDto); + NoteResponseDto noteResponseDto = noteService.create(noteRequestDto, MEMBER_ID); //then assertThat(noteResponseDto.getTitle()).isNull(); diff --git a/BE/src/test/java/dev/whatevernote/be/service/note/NoteDeleteTest.java b/BE/src/test/java/dev/whatevernote/be/service/note/NoteDeleteTest.java index ad5d7f2..53104bc 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/note/NoteDeleteTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/note/NoteDeleteTest.java @@ -48,19 +48,19 @@ class NormalDeleteTest { @Test void soft_delete_note(){ //given - List notes = noteRepository.findAllByOrderBySeq(); + List notes = noteRepository.findAllByMemberIdOrderBySeq(MEMBER_ID); int deleteNoteId = notes.get(notes.size()-1).getId(); int numberOfNote = notes.size(); //when - noteService.delete(deleteNoteId); + noteService.delete(deleteNoteId, MEMBER_ID); Optional note = noteRepository.findById(deleteNoteId); - List afterDelete = noteRepository.findAllByOrderBySeq(); + List afterDelete = noteRepository.findAllByMemberIdOrderBySeq(MEMBER_ID); //then assertThat(note).isEmpty(); assertThat(afterDelete).hasSize(numberOfNote-1); - assertThatThrownBy(() -> noteService.findById(deleteNoteId)) + assertThatThrownBy(() -> noteService.findById(deleteNoteId, MEMBER_ID)) .isInstanceOf(NotFoundNoteException.class) .hasMessageContaining(ErrorCodeAndMessages.E404_NOT_FOUND_NOTE.getMessage()); } @@ -69,26 +69,26 @@ void soft_delete_note(){ @Test void soft_delete_note_and_card_and_content(){ //given - List notes = noteRepository.findAllByOrderBySeq(); + List notes = noteRepository.findAllByMemberIdOrderBySeq(MEMBER_ID); int deleteNoteId = notes.get(0).getId(); - List cards = cardRepository.findAllByNoteId(deleteNoteId); + List cards = cardRepository.findAllByNoteIdOrderBySeq(deleteNoteId); List contents = new ArrayList<>(); for (Card card : cards) { - contents.addAll(contentRepository.findAllByCardId(card.getId())); + contents.addAll(contentRepository.findAllByCardIdOrderBySeqAsc(card.getId())); } int numberOfNote = notes.size(); //when - noteService.delete(deleteNoteId); + noteService.delete(deleteNoteId, MEMBER_ID); Optional note = noteRepository.findById(deleteNoteId); - List notesAfterDelete = noteRepository.findAllByOrderBySeq(); + List notesAfterDelete = noteRepository.findAllByMemberIdOrderBySeq(MEMBER_ID); //then // note assertThat(note).isEmpty(); assertThat(notesAfterDelete).hasSize(numberOfNote-1); - assertThatThrownBy(() -> noteService.findById(deleteNoteId)) + assertThatThrownBy(() -> noteService.findById(deleteNoteId, MEMBER_ID)) .isInstanceOf(NotFoundNoteException.class) .hasMessageContaining(ErrorCodeAndMessages.E404_NOT_FOUND_NOTE.getMessage()); diff --git a/BE/src/test/java/dev/whatevernote/be/service/note/NoteFindTest.java b/BE/src/test/java/dev/whatevernote/be/service/note/NoteFindTest.java index fddb0dc..28e33dd 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/note/NoteFindTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/note/NoteFindTest.java @@ -39,13 +39,13 @@ class NormalFindOneTest { @Test void normal_find_one() { //given - List notes = noteRepository.findAllByOrderBySeq(); + List notes = noteRepository.findAllByMemberIdOrderBySeq(MEMBER_ID); int seq = 2; int tmpNoteId = notes.get(2).getId(); //when - NoteResponseDto noteResponseDto = noteService.findById(tmpNoteId); + NoteResponseDto noteResponseDto = noteService.findById(tmpNoteId, MEMBER_ID); //then assertThat(noteResponseDto.getId()).isEqualTo(tmpNoteId); @@ -68,7 +68,7 @@ class NormalFindAllTest { void normal_find_all() { //given PageRequest defaultPageRequest = PageRequest.of(PAGE_NUMBER, PAGE_SIZE); - NoteResponseDtos noteRequestDtos = noteService.findAll(defaultPageRequest); + NoteResponseDtos noteRequestDtos = noteService.findAll(MEMBER_ID, defaultPageRequest); //when List notes = noteRequestDtos.getNotes(); diff --git a/BE/src/test/java/dev/whatevernote/be/service/note/NoteUpdateTest.java b/BE/src/test/java/dev/whatevernote/be/service/note/NoteUpdateTest.java index 271bbbe..f78da63 100644 --- a/BE/src/test/java/dev/whatevernote/be/service/note/NoteUpdateTest.java +++ b/BE/src/test/java/dev/whatevernote/be/service/note/NoteUpdateTest.java @@ -38,14 +38,14 @@ class NormalTitleUpdateTest { @Test void note_title_update(){ //given - List notes = noteRepository.findAllByOrderBySeq(); + List notes = noteRepository.findAllByMemberIdOrderBySeq(MEMBER_ID); int seq = 2; int updateNoteId = notes.get(seq).getId(); String updateTitle = "제목 바꾼 노트"; NoteRequestDto noteRequestDto = new NoteRequestDto(null, updateTitle); //when - NoteResponseDto noteResponseDto = noteService.update(updateNoteId, noteRequestDto); + NoteResponseDto noteResponseDto = noteService.update(updateNoteId, noteRequestDto, MEMBER_ID); //then assertThat(noteResponseDto.getId()).isEqualTo(updateNoteId); @@ -62,13 +62,13 @@ class NormalSeqUpdateTest { @Test void note_seq_update_to_first(){ //given - List notes = noteRepository.findAllByOrderBySeq(); + List notes = noteRepository.findAllByMemberIdOrderBySeq(MEMBER_ID); int updateNoteId = 1; int updateSeq = 0; NoteRequestDto noteRequestDto = new NoteRequestDto(updateSeq, null); //when - NoteResponseDto noteResponseDto = noteService.update(updateNoteId, noteRequestDto); + NoteResponseDto noteResponseDto = noteService.update(updateNoteId, noteRequestDto, MEMBER_ID); //then assertThat(noteResponseDto.getId()).isEqualTo(updateNoteId); @@ -79,7 +79,7 @@ void note_seq_update_to_first(){ @Test void note_seq_update(){ //given - List notes = noteRepository.findAllByOrderBySeq(); + List notes = noteRepository.findAllByMemberIdOrderBySeq(MEMBER_ID); int seq = 2; int updateNoteId = notes.get(seq).getId(); int updateSeq = 1; @@ -88,7 +88,7 @@ void note_seq_update(){ NoteRequestDto noteRequestDto = new NoteRequestDto(updateSeq, null); //when - NoteResponseDto noteResponseDto = noteService.update(updateNoteId, noteRequestDto); + NoteResponseDto noteResponseDto = noteService.update(updateNoteId, noteRequestDto, MEMBER_ID); //then assertThat(noteResponseDto.getId()).isEqualTo(updateNoteId); @@ -99,14 +99,14 @@ void note_seq_update(){ @Test void note_seq_update_to_last(){ //given - List notes = noteRepository.findAllByOrderBySeq(); + List notes = noteRepository.findAllByMemberIdOrderBySeq(MEMBER_ID); int seq = 1; int updateNoteId = notes.get(seq).getId(); int updateSeq = NUMBER_OF_NOTE; NoteRequestDto noteRequestDto = new NoteRequestDto(updateSeq, null); //when - NoteResponseDto noteResponseDto = noteService.update(updateNoteId, noteRequestDto); + NoteResponseDto noteResponseDto = noteService.update(updateNoteId, noteRequestDto, MEMBER_ID); //then assertThat(noteResponseDto.getId()).isEqualTo(updateNoteId); @@ -117,14 +117,14 @@ void note_seq_update_to_last(){ @Test void note_seq_update_to_same_seq(){ //given - List notes = noteRepository.findAllByOrderBySeq(); + List notes = noteRepository.findAllByMemberIdOrderBySeq(MEMBER_ID); int seq = 1; int updateNoteId = notes.get(seq).getId(); int updateSeq = 2; NoteRequestDto noteRequestDto = new NoteRequestDto(updateSeq, null); //when - NoteResponseDto noteResponseDto = noteService.update(updateNoteId, noteRequestDto); + NoteResponseDto noteResponseDto = noteService.update(updateNoteId, noteRequestDto, MEMBER_ID); //then assertThat(noteResponseDto.getId()).isEqualTo(updateNoteId); diff --git a/BE/src/test/java/dev/whatevernote/be/tool/AuthMocks.java b/BE/src/test/java/dev/whatevernote/be/tool/AuthMocks.java new file mode 100644 index 0000000..3d41273 --- /dev/null +++ b/BE/src/test/java/dev/whatevernote/be/tool/AuthMocks.java @@ -0,0 +1,56 @@ +package dev.whatevernote.be.tool; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED; +import static java.nio.charset.Charset.defaultCharset; +import static org.springframework.util.StreamUtils.copyToString; + +import com.github.tomakehurst.wiremock.client.WireMock; +import java.io.IOException; +import java.io.InputStream; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +public class AuthMocks { + + public static void setUpResponses() throws IOException { + setUpMockTokenResponse(); + setUpMockMemberInfoResponse(); + } + + private static void setUpMockTokenResponse() throws IOException { + stubFor(WireMock.post(urlEqualTo( + "/oauth/token?grant_type=authorization_code&client_id=whatever-note&redirect_uri=redirectURI&code=code")) + .willReturn(aResponse() + .withStatus(HttpStatus.OK.value()) + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(getMockResponseBodyByPath("payload/get-token-response.json")) + )); + } + + private static String getMockResponseBodyByPath(String path) throws IOException { + return copyToString(gretMockResourceStream(path), defaultCharset()); + } + + private static InputStream gretMockResourceStream(String path) { + return AuthMocks.class.getClassLoader().getResourceAsStream(path); + } + + public static void setUpMockMemberInfoResponse() throws IOException { + stubFor(WireMock.get(urlEqualTo("/v2/user/me")) + .inScenario("Kakao Login Success") + .whenScenarioStateIs(STARTED) + .withHeader("Authorization", equalTo("Bearer accessToken")) + .willReturn(aResponse() + .withStatus(HttpStatus.OK.value()) + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody(getMockResponseBodyByPath("payload/get-member-info-response.json")) + ) + ); + } + + +} diff --git a/BE/src/test/java/dev/whatevernote/be/tool/DataBaseConfigurator.java b/BE/src/test/java/dev/whatevernote/be/tool/DataBaseConfigurator.java index 35c4ef1..4cb11d8 100644 --- a/BE/src/test/java/dev/whatevernote/be/tool/DataBaseConfigurator.java +++ b/BE/src/test/java/dev/whatevernote/be/tool/DataBaseConfigurator.java @@ -1,5 +1,7 @@ package dev.whatevernote.be.tool; +import dev.whatevernote.be.login.repository.MemberRepository; +import dev.whatevernote.be.login.service.domain.Member; import dev.whatevernote.be.repository.CardRepository; import dev.whatevernote.be.repository.ContentRepository; import dev.whatevernote.be.repository.NoteRepository; @@ -27,6 +29,8 @@ public class DataBaseConfigurator implements InitializingBean { private static final String SET_FOREIGN_KEY_CHECKS = "SET FOREIGN_KEY_CHECKS = "; private static final String TRUNCATE_TABLE = "TRUNCATE TABLE "; + + private static final int NUMBER_OF_MEMBER = 2; private static final int NUMBER_OF_NOTE = 3; private static final int NUMBER_OF_CARD = 3; private static final int NUMBER_OF_CONTENT = 5; @@ -37,6 +41,9 @@ public class DataBaseConfigurator implements InitializingBean { private static final String CONTENT_IMAGE_EXTENSION = ".png"; private static final int DEFAULT_RANGE = 1_000; + @Autowired + private MemberRepository memberRepository; + @Autowired private NoteRepository noteRepository; @@ -86,18 +93,32 @@ private void cleanUpDatabase(Connection connection) throws SQLException { } public void initDataSource() { + initMember(); initNoteData(); initCardData(); initContentData(); } + private void initMember() { + for (int i = 1; i <= NUMBER_OF_MEMBER; i++) { + memberRepository.save( + new Member("uniqueId" + i, "nickname" + i, + "abcd1234" + i + "@naver.com", + "https://dummydata.com") + );} + } + private void initNoteData() { + Member member = memberRepository.findById(1L) + .orElseThrow(() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다.")); for (int i = 1; i <= NUMBER_OF_NOTE; i++) { noteRepository.save( Note.from( - new NoteRequestDto(i * DEFAULT_RANGE, NOTE_TITLE + i) + new NoteRequestDto(i * DEFAULT_RANGE, NOTE_TITLE + i), + member ) ); + } } @@ -122,7 +143,7 @@ private void initContentData() { contentRepository.save( Content.from( new ContentRequestDto( - CONTENT_STRING_INFO, cnt++ * DEFAULT_RANGE, Boolean.FALSE + CONTENT_STRING_INFO+(i-1), cnt++ * DEFAULT_RANGE, Boolean.FALSE ), cardRepository.findById(cardId) .orElseThrow(() -> new IllegalArgumentException("해당 카드가 존재하지 않습니다.")) diff --git a/BE/src/test/java/dev/whatevernote/be/tool/TestWebConfig.java b/BE/src/test/java/dev/whatevernote/be/tool/TestWebConfig.java new file mode 100644 index 0000000..960a9d0 --- /dev/null +++ b/BE/src/test/java/dev/whatevernote/be/tool/TestWebConfig.java @@ -0,0 +1,43 @@ +package dev.whatevernote.be.tool; + +import dev.whatevernote.be.config.WebConfig; +import dev.whatevernote.be.login.AuthInterceptor; +import dev.whatevernote.be.login.LoginArgumentResolver; +import dev.whatevernote.be.login.config.properties.JwtProperties; +import dev.whatevernote.be.login.service.provider.JwtProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@TestConfiguration +@EnableConfigurationProperties(value = {JwtProperties.class}) +public class TestWebConfig { + + @Autowired + JwtProperties jwtProperties; + + @Bean + WebConfig getWebConfig() { + return new WebConfig(getAuthInterceptor(), getArgumentResolver()); + } + + @Bean + @Primary + AuthInterceptor getAuthInterceptor() { + return new AuthInterceptor(getJwtProvider()); + } + + @Bean + @Primary + LoginArgumentResolver getArgumentResolver() { + return new LoginArgumentResolver(getJwtProvider()); + } + + @Bean + JwtProvider getJwtProvider() { + return new JwtProvider(jwtProperties); + } + +} diff --git a/BE/src/test/java/dev/whatevernote/be/web/auth/AuthAccessTokenReIssueTest.java b/BE/src/test/java/dev/whatevernote/be/web/auth/AuthAccessTokenReIssueTest.java new file mode 100644 index 0000000..ed7c81b --- /dev/null +++ b/BE/src/test/java/dev/whatevernote/be/web/auth/AuthAccessTokenReIssueTest.java @@ -0,0 +1,144 @@ +package dev.whatevernote.be.web.auth; + +import static dev.whatevernote.be.common.ResponseCodeAndMessages.REISSUE_ACCESS_TOKEN_SUCCESS; +import static dev.whatevernote.be.exception.ErrorCodeAndMessages.E400_INVALID_JWT_TOKEN; +import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.whatevernote.be.common.BaseResponse; +import dev.whatevernote.be.login.config.properties.JwtProperties; +import dev.whatevernote.be.login.service.LoginService; +import dev.whatevernote.be.login.service.provider.JwtProvider; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +@DisplayName("OAuth Access Token 재발급 테스트") +class AuthAccessTokenReIssueTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private JwtProvider jwtProvider; + + @Autowired + private JwtProperties jwtProperties; + + @MockBean + private LoginService loginService; + + @BeforeEach + void init(WebApplicationContext wc) { + this.mockMvc = MockMvcBuilders + .webAppContextSetup(wc) + .addFilter(new CharacterEncodingFilter("UTF-8", true)) + .build(); + } + + @Nested + @DisplayName("Access Token을 재발급 받을 때") + class ReIssueTest { + + @Nested + @DisplayName("Refresh Token이 유효하다면") + class NormalTest { + + @Test + @DisplayName("새로운 Access Token이 발급된다.") + void reissue_access_token() throws Exception { + //given + Long memberId = 1L; + String refreshToken = jwtProvider.generateRefreshToken(memberId); + String reIssueAccessToken = jwtProvider.generateAccessToken(memberId); + BaseResponse baseResponse = new BaseResponse<>(REISSUE_ACCESS_TOKEN_SUCCESS, reIssueAccessToken); + + when(loginService.reIssueAccessToken(refEq(memberId))) + .thenReturn(reIssueAccessToken); + + //when + ResultActions resultActions = mockMvc.perform(get("/login/re-issue") + .header("Authorization", String.format("%s %s", jwtProperties.getTokenType(), refreshToken)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON_VALUE) + .content(objectMapper.writeValueAsString(reIssueAccessToken))); + + //then + resultActions.andExpect(status().isOk()) + .andExpect(content().string(objectMapper.writeValueAsString(baseResponse))) + .andDo(print()); + + verify(loginService).reIssueAccessToken(refEq(memberId)); + } + } + + @Nested + @DisplayName("Refresh Token이 유효하지 않다면") + class AbNormalTest { + @Test + @DisplayName("E400_INVALID_JWT_TOKEN 이 발생한다.") + void invalid_refresh_token() throws Exception { + //given + Long memberId = 1L; + SecretKey secretKey = Keys.hmacShaKeyFor(jwtProperties.getSecretKey().getBytes( + StandardCharsets.UTF_8)); + String refreshToken = Jwts.builder() + .setIssuer(jwtProperties.getIssuer()) + .setSubject(jwtProperties.getRefreshTokenSubject()) + .setAudience(String.valueOf(memberId)) + .setExpiration(new Date()) + .signWith(secretKey) + .compact(); + + String reIssueAccessToken = jwtProvider.generateAccessToken(memberId); + BaseResponse baseResponse = new BaseResponse<>(E400_INVALID_JWT_TOKEN, null); + + when(loginService.reIssueAccessToken(refEq(memberId))) + .thenReturn(reIssueAccessToken); + + //when + ResultActions resultActions = mockMvc.perform(get("/login/re-issue") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", String.format("%s %s", jwtProperties.getTokenType(), refreshToken)) + .content(objectMapper.writeValueAsString(reIssueAccessToken))); + + //then + resultActions.andExpect(status().isOk()) + .andExpect(content().string(objectMapper.writeValueAsString(baseResponse))) + .andDo(print()); + } + } + + } + +} diff --git a/BE/src/test/java/dev/whatevernote/be/web/auth/AuthLoginIntegrationTest.java b/BE/src/test/java/dev/whatevernote/be/web/auth/AuthLoginIntegrationTest.java new file mode 100644 index 0000000..2acd0a3 --- /dev/null +++ b/BE/src/test/java/dev/whatevernote/be/web/auth/AuthLoginIntegrationTest.java @@ -0,0 +1,90 @@ +package dev.whatevernote.be.web.auth; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import dev.whatevernote.be.tool.AuthMocks; +import dev.whatevernote.be.login.repository.MemberRepository; +import dev.whatevernote.be.login.service.LoginService; +import dev.whatevernote.be.login.service.domain.Member; +import dev.whatevernote.be.login.service.dto.response.LoginResponse; +import dev.whatevernote.be.login.service.provider.JwtProvider; +import java.io.IOException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +@AutoConfigureWireMock(port = 0) +@TestPropertySource(properties = { + "oauth.kakao.token-url=http://localhost:${wiremock.server.port}/oauth/token", + "oauth.kakao.resource-url=http://localhost:${wiremock.server.port}/v2/user/me" +}) +@DisplayName("Auth Login 통합 테스트") +@SpringBootTest +@ActiveProfiles("test") +class AuthLoginIntegrationTest { + + @Autowired + private LoginService loginService; + + @Autowired + private JwtProvider jwtProvider; + + @Autowired + private MemberRepository memberRepository; + + @BeforeEach + void init() throws IOException { + memberRepository.deleteAllInBatch(); + AuthMocks.setUpResponses(); + } + + @Test + @DisplayName("신규 로그인을 시도했을 때, 로그인 성공 및 멤버 생성 성공") + void auth_login_success_with_new_member_create(){ + //given + WireMock.setScenarioState("Kakao Login Success", "Started"); + + //when + LoginResponse response = loginService.login("code"); + String accessToken = response.getAccessToken(); + String refreshToken = response.getRefreshToken(); + Long memberId = response.getMemberId(); + + //then + assertThat(response.getNickname()).isEqualTo("홍길동"); + assertThat(jwtProvider.getAudience(accessToken)).isEqualTo(memberId.toString()); + assertThat(jwtProvider.getAudience(refreshToken)).isEqualTo(memberId.toString()); + assertThat(response.getProfileImage()).isEqualTo("http://yyy.kakao.com/dn/img_640x640.jpg"); + } + + @Test + @DisplayName("기존 회원 로그인을 시도했을 때, 로그인 성공") + void auth_login_success(){ + //given + memberRepository.save(new Member( + "123456789", + "홍길동", + "sample@sample.com", + "http://yyy.kakao.com/dn/img_640x640.jpg" + )); + WireMock.setScenarioState("Kakao Login Success", "Started"); + + //when + LoginResponse response = loginService.login("code"); + String accessToken = response.getAccessToken(); + String refreshToken = response.getRefreshToken(); + Long memberId = response.getMemberId(); + + //then + assertThat(response.getNickname()).isEqualTo("홍길동"); + assertThat(jwtProvider.getAudience(accessToken)).isEqualTo(memberId.toString()); + assertThat(jwtProvider.getAudience(refreshToken)).isEqualTo(memberId.toString()); + assertThat(response.getProfileImage()).isEqualTo("http://yyy.kakao.com/dn/img_640x640.jpg"); + } +} diff --git a/BE/src/test/java/dev/whatevernote/be/web/controller/CardControllerTest.java b/BE/src/test/java/dev/whatevernote/be/web/controller/CardControllerTest.java index 25ab049..ce967fc 100644 --- a/BE/src/test/java/dev/whatevernote/be/web/controller/CardControllerTest.java +++ b/BE/src/test/java/dev/whatevernote/be/web/controller/CardControllerTest.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import dev.whatevernote.be.common.BaseResponse; +import dev.whatevernote.be.login.service.domain.Member; import dev.whatevernote.be.service.CardService; import dev.whatevernote.be.service.domain.Card; import dev.whatevernote.be.service.domain.Content; @@ -36,6 +37,8 @@ import dev.whatevernote.be.service.dto.response.CardDetailResponseDto; import dev.whatevernote.be.service.dto.response.CardResponseDto; import dev.whatevernote.be.service.dto.response.CardResponseDtos; +import dev.whatevernote.be.tool.TestWebConfig; +import dev.whatevernote.be.login.service.provider.JwtProvider; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -45,6 +48,7 @@ import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; @@ -56,12 +60,14 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; +@Import(TestWebConfig.class) @AutoConfigureRestDocs @ExtendWith({RestDocumentationExtension.class}) @WebMvcTest(CardController.class) class CardControllerTest { private static final long DEFAULT_RANGE = 1_000L; + private static final long MEMBER_ID = 1; private static final int NOTE_ID = 1; private static final long CARD_ID = 1; private static final String TEMP_IMAGE_URL = "https://en.wikipedia.org/wiki/Image#/media/File:Image_created_with_a_mobile_phone.png"; @@ -69,9 +75,13 @@ class CardControllerTest { @Autowired private MockMvc mockMvc; + @Autowired + private JwtProvider jwtProvider; + @MockBean private CardService cardService; + private final ObjectMapper objectMapper = new ObjectMapper(); @BeforeEach @@ -90,12 +100,13 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov CardRequestDto cardRequestDto = new CardRequestDto(expectedCardId, "첫번째 카드"); CardResponseDto cardResponseDto = new CardResponseDto(expectedCardId, "첫번째 카드", DEFAULT_RANGE, NOTE_ID); - when(cardService.create(refEq(cardRequestDto), refEq(NOTE_ID))).thenReturn(cardResponseDto); + when(cardService.create(refEq(cardRequestDto), refEq(NOTE_ID), refEq(MEMBER_ID))).thenReturn(cardResponseDto); BaseResponse baseResponse = new BaseResponse<>(CARD_CREATE_SUCCESS, cardResponseDto); //when ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders .post("/api/note/{NOTE_ID}/card", NOTE_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .content(objectMapper.writeValueAsString(cardRequestDto)) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -129,7 +140,9 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov void 단어장id와_카드id를_조회하면_해당_카드를_반환한다() throws Exception { //given NoteRequestDto noteRequestDto = new NoteRequestDto(NOTE_ID, "첫번째 노트"); - Note note = Note.from(noteRequestDto); + + Note note = Note.from(noteRequestDto, + new Member("1234", "홍길동", "hgd1234@naver.com", "https://dummy-img.co.kr")); CardRequestDto cardRequestDto = new CardRequestDto(CARD_ID, "첫번째 카드"); Card card = Card.from(cardRequestDto, note); ContentRequestDto contentRequestDto1 = new ContentRequestDto("첫 번째 컨텐트", DEFAULT_RANGE, Boolean.FALSE); @@ -139,12 +152,13 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov contents.add(Content.from(contentRequestDto2, card)); CardDetailResponseDto cardDetailResponseDto = CardDetailResponseDto.from(card, NOTE_ID, contents); - when(cardService.findById(NOTE_ID, CARD_ID)).thenReturn(cardDetailResponseDto); + when(cardService.findById(NOTE_ID, CARD_ID, MEMBER_ID)).thenReturn(cardDetailResponseDto); BaseResponse baseResponse = new BaseResponse<>(CARD_RETRIEVE_DETAIL_SUCCESS, cardDetailResponseDto); //when ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders .get("/api/note/{NOTE_ID}/card/{CARD_ID}", NOTE_ID, CARD_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -183,13 +197,14 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov dtos.add(new CardResponseDto(3L, "card-3", DEFAULT_RANGE*3, NOTE_ID)); CardResponseDtos cardResponseDtos = new CardResponseDtos(dtos, false, 0); - when(cardService.findAll(any(), any())).thenReturn(cardResponseDtos); + when(cardService.findAll(any(), any(), any())).thenReturn(cardResponseDtos); BaseResponse baseResponse = new BaseResponse<>(CARD_RETRIEVE_ALL_SUCCESS, cardResponseDtos); //when ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders .get("/api/note/{NOTE_ID}/card?page=0&size=5", NOTE_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -224,12 +239,13 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov //given CardRequestDto cardRequestDto = new CardRequestDto(0L, null); CardResponseDto cardResponseDto = new CardResponseDto(CARD_ID, "변경될 제목", DEFAULT_RANGE, 1); - when(cardService.update(any(), any(), any())).thenReturn(cardResponseDto); + when(cardService.update(any(), any(), any(), any())).thenReturn(cardResponseDto); BaseResponse baseResponse = new BaseResponse<>(CARD_MODIFY_SUCCESS, cardResponseDto); //when ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders .put("/api/note/{NOTE_ID}/card/{CARD_ID}", NOTE_ID, CARD_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .content(objectMapper.writeValueAsString(cardRequestDto)) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -271,13 +287,15 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov @Test void 카드를_삭제하면_soft_delete_한다() throws Exception { //given - doNothing().when(cardService).delete(any()); + doNothing().when(cardService).delete(any(), any(), any()); BaseResponse baseResponse = new BaseResponse<>(CARD_REMOVE_SUCCESS, null); //when ResultActions resultActions = this.mockMvc .perform(RestDocumentationRequestBuilders - .delete("/api/note/{NOTE_ID}/card/{CARD_ID}", NOTE_ID, CARD_ID)); + .delete("/api/note/{NOTE_ID}/card/{CARD_ID}", NOTE_ID, CARD_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) + ); //then resultActions.andExpect(status().isOk()) diff --git a/BE/src/test/java/dev/whatevernote/be/web/controller/ContentControllerTest.java b/BE/src/test/java/dev/whatevernote/be/web/controller/ContentControllerTest.java index 0b9fc4a..b379599 100644 --- a/BE/src/test/java/dev/whatevernote/be/web/controller/ContentControllerTest.java +++ b/BE/src/test/java/dev/whatevernote/be/web/controller/ContentControllerTest.java @@ -29,6 +29,8 @@ import dev.whatevernote.be.service.dto.request.ContentRequestDto; import dev.whatevernote.be.service.dto.response.ContentResponseDto; import dev.whatevernote.be.service.dto.response.ContentResponseDtos; +import dev.whatevernote.be.tool.TestWebConfig; +import dev.whatevernote.be.login.service.provider.JwtProvider; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -38,6 +40,7 @@ import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; @@ -49,11 +52,12 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; +@Import(TestWebConfig.class) @AutoConfigureRestDocs @ExtendWith({RestDocumentationExtension.class}) @WebMvcTest(ContentController.class) class ContentControllerTest { - + private static final long MEMBER_ID = 1; private static final long CONTENT_ID = 1; private static final long CARD_ID = 1; private static final int NOTE_ID = 1; @@ -62,6 +66,9 @@ class ContentControllerTest { @Autowired private MockMvc mockMvc; + @Autowired + private JwtProvider jwtProvider; + @MockBean private ContentService contentService; @@ -85,13 +92,14 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov dtos.add(new ContentResponseDto(3L, DEFAULT_RANGE*3, "content-3", Boolean.FALSE, CARD_ID)); ContentResponseDtos contentResponseDtos = new ContentResponseDtos(dtos, false, 0); - when(contentService.findAll(any(), any())).thenReturn(contentResponseDtos); + when(contentService.findAll(any(), any(), any(), any())).thenReturn(contentResponseDtos); BaseResponse baseResponse = new BaseResponse<>(CONTENT_RETRIEVE_ALL_SUCCESS, contentResponseDtos); //when ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders .get("/api/note/{NOTE_ID}/card/{CARD_ID}/content?page=0&size=5", NOTE_ID, CARD_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -130,12 +138,13 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov ContentRequestDto contentRequestDto = new ContentRequestDto("첫 번째 컨텐츠", tmpSeq, Boolean.FALSE); ContentResponseDto contentResponseDto = new ContentResponseDto(CONTENT_ID, DEFAULT_RANGE, "첫 번째 컨텐츠", Boolean.FALSE, CARD_ID); BaseResponse baseResponse = new BaseResponse<>(CONTENT_CREATE_SUCCESS, contentResponseDto); - when(contentService.create(refEq(contentRequestDto), refEq(CARD_ID))).thenReturn(contentResponseDto); + when(contentService.create(refEq(contentRequestDto), refEq(NOTE_ID), refEq(CARD_ID), refEq(MEMBER_ID))).thenReturn(contentResponseDto); //when ResultActions resultActions = this.mockMvc.perform( RestDocumentationRequestBuilders .post("/api/note/{NOTE_ID}/card/{CARD_ID}/content", NOTE_ID, CARD_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .content(objectMapper.writeValueAsString(contentRequestDto)) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -178,12 +187,13 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov ContentRequestDto contentRequestDto = new ContentRequestDto("수정된 컨텐츠", tmpSeq, Boolean.FALSE); ContentResponseDto contentResponseDto = new ContentResponseDto(CONTENT_ID, DEFAULT_RANGE, "수정된 컨텐츠", Boolean.FALSE, CARD_ID); BaseResponse baseResponse = new BaseResponse<>(CONTENT_MODIFY_SUCCESS, contentResponseDto); - when(contentService.update(any(), any(), any())).thenReturn(contentResponseDto); + when(contentService.update(any(), any(), any(), any(), any())).thenReturn(contentResponseDto); //when ResultActions resultActions = this.mockMvc.perform( RestDocumentationRequestBuilders .put("/api/note/{NOTE_ID}/card/{CARD_ID}/content/{CONTENT_ID}", NOTE_ID, CARD_ID, CONTENT_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .content(objectMapper.writeValueAsString(contentRequestDto)) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -223,14 +233,16 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov @Test void 카드를_삭제하면_soft_delete_한다() throws Exception { //given - doNothing().when(contentService).delete(any()); + doNothing().when(contentService).delete(any(), any(), any()); BaseResponse baseResponse = new BaseResponse<>(CONTENT_REMOVE_SUCCESS, null); //when ResultActions resultActions = this.mockMvc .perform(RestDocumentationRequestBuilders .delete("/api/note/{NOTE_ID}/card/{CARD_ID}/content/{CONTENT_ID}", - NOTE_ID, CARD_ID, CONTENT_ID)); + NOTE_ID, CARD_ID, CONTENT_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) + ); //then resultActions.andExpect(status().isOk()) diff --git a/BE/src/test/java/dev/whatevernote/be/web/controller/NoteControllerTest.java b/BE/src/test/java/dev/whatevernote/be/web/controller/NoteControllerTest.java index 9cb755b..809780a 100644 --- a/BE/src/test/java/dev/whatevernote/be/web/controller/NoteControllerTest.java +++ b/BE/src/test/java/dev/whatevernote/be/web/controller/NoteControllerTest.java @@ -30,6 +30,8 @@ import dev.whatevernote.be.service.dto.request.NoteRequestDto; import dev.whatevernote.be.service.dto.response.NoteResponseDto; import dev.whatevernote.be.service.dto.response.NoteResponseDtos; +import dev.whatevernote.be.tool.TestWebConfig; +import dev.whatevernote.be.login.service.provider.JwtProvider; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -39,6 +41,7 @@ import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; @@ -51,22 +54,26 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; +@Import(TestWebConfig.class) @AutoConfigureRestDocs @ExtendWith({RestDocumentationExtension.class}) @WebMvcTest(NoteController.class) class NoteControllerTest { + private static final long MEMBER_ID = 1; private static final int DEFAULT_RANGE = 1_000; @Autowired private MockMvc mockMvc; + @Autowired + private JwtProvider jwtProvider; + @MockBean private NoteService noteService; private final ObjectMapper objectMapper = new ObjectMapper(); private final Integer NOTE_ID = 1; - @BeforeEach public void init(WebApplicationContext wc, RestDocumentationContextProvider provider) { this.mockMvc = MockMvcBuilders.webAppContextSetup(wc) @@ -81,12 +88,13 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov void 단어장을_id에_따라_조회하면_해당_단어장을_반환한다() throws Exception { //given NoteResponseDto noteResponseDto = new NoteResponseDto(NOTE_ID, 1, "note-1"); - when(noteService.findById(1)).thenReturn(noteResponseDto); + when(noteService.findById(1, MEMBER_ID)).thenReturn(noteResponseDto); BaseResponse baseResponse = new BaseResponse<>(NOTE_RETRIEVE_DETAIL_SUCCESS, noteResponseDto); //when ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders .get("/api/note/{NOTE_ID}", NOTE_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -117,11 +125,12 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov dtos.add(new NoteResponseDto(2, DEFAULT_RANGE*2, "note-2")); dtos.add(new NoteResponseDto(3, DEFAULT_RANGE*3, "note-3")); NoteResponseDtos noteResponseDtos = new NoteResponseDtos(dtos, false, 0); - when(noteService.findAll(any())).thenReturn(noteResponseDtos); + when(noteService.findAll(any(), any())).thenReturn(noteResponseDtos); BaseResponse baseResponse = new BaseResponse<>(NOTE_RETRIEVE_ALL_SUCCESS, noteResponseDtos); //when ResultActions resultActions = this.mockMvc.perform(MockMvcRequestBuilders.get("/api/note?page=0&size=5") + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -152,11 +161,12 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov //given NoteRequestDto noteRequestDto = new NoteRequestDto(1, "첫번째 노트"); NoteResponseDto noteResponseDto = new NoteResponseDto(NOTE_ID, 1, "첫번째 노트"); - when(noteService.create(refEq(noteRequestDto))).thenReturn(noteResponseDto); + when(noteService.create(refEq(noteRequestDto), refEq(MEMBER_ID))).thenReturn(noteResponseDto); BaseResponse baseResponse = new BaseResponse<>(NOTE_CREATE_SUCCESS, noteResponseDto); //when ResultActions resultActions = this.mockMvc.perform(MockMvcRequestBuilders.post("/api/note") + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .content(objectMapper.writeValueAsString(noteRequestDto)) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -185,13 +195,14 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov //given NoteRequestDto noteRequestDto = new NoteRequestDto(0, null); NoteResponseDto noteResponseDto = new NoteResponseDto(NOTE_ID, DEFAULT_RANGE, "단어장 제목 제목"); - when(noteService.update(any(), any())).thenReturn(noteResponseDto); + when(noteService.update(any(), any(), any())).thenReturn(noteResponseDto); BaseResponse baseResponse = new BaseResponse<>(NOTE_MODIFY_SUCCESS, noteResponseDto); //when ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders .put("/api/note/{NOTE_ID}", NOTE_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) .content(objectMapper.writeValueAsString(noteRequestDto)) .contentType(MediaType.APPLICATION_JSON_VALUE)); @@ -230,13 +241,15 @@ public void init(WebApplicationContext wc, RestDocumentationContextProvider prov @Test void 단어장을_삭제하면_soft_delete_한다() throws Exception { //given - doNothing().when(noteService).delete(any()); + doNothing().when(noteService).delete(any(), any()); BaseResponse baseResponse = new BaseResponse<>(NOTE_REMOVE_SUCCESS, null); //when ResultActions resultActions = this.mockMvc .perform(RestDocumentationRequestBuilders - .delete("/api/note/{NOTE_ID}", NOTE_ID)); + .delete("/api/note/{NOTE_ID}", NOTE_ID) + .header("Authorization", "Bearer "+jwtProvider.generateAccessToken(MEMBER_ID)) + ); //then resultActions.andExpect(status().isOk()) diff --git a/BE/src/test/resources/application.yml b/BE/src/test/resources/application.yml index 15bc5ac..fdfea65 100644 --- a/BE/src/test/resources/application.yml +++ b/BE/src/test/resources/application.yml @@ -18,3 +18,23 @@ logging: org.hibernate.SQL: debug dev.whatevernote: be: debug + +oauth: + kakao: + response-type: code + client-id: whatever-note + redirect-uri: redirectURI + token-type: Bearer + content-type: application/x-www-form-urlencoded;charset=utf-8 + grant-type: authorization_code + token-url: tokenURL + resource-url: ResourceURL + +jwt: + secret-key: project-test*project-test*project-test + access-expire-time: 10000 + access-token-subject: project-test-access-token + refresh-expire-time: 10000000000 + refresh-token-subject: project-test-refresh-token + issuer: whatever-note-server + token-type: Bearer diff --git a/BE/src/test/resources/payload/get-member-info-response.json b/BE/src/test/resources/payload/get-member-info-response.json new file mode 100644 index 0000000..1a0861b --- /dev/null +++ b/BE/src/test/resources/payload/get-member-info-response.json @@ -0,0 +1,24 @@ +{ + "id": 123456789, + "kakao_account": { + "profile_needs_agreement": false, + "profile": { + "nickname": "홍길동", + "thumbnail_image_url": "http://yyy.kakao.com/img_110x110.jpg", + "profile_image_url": "http://yyy.kakao.com/dn/img_640x640.jpg", + "is_default_image": false + }, + "name_needs_agreement": false, + "name": "홍길동", + "email_needs_agreement": false, + "is_email_valid": true, + "is_email_verified": true, + "email": "sample@sample.com", + "age_range_needs_agreement": false, + "age_range": "20~29", + "birthday_needs_agreement": false, + "birthday": "1130", + "gender_needs_agreement": false, + "gender": "female" + } +} diff --git a/BE/src/test/resources/payload/get-token-response.json b/BE/src/test/resources/payload/get-token-response.json new file mode 100644 index 0000000..1506e96 --- /dev/null +++ b/BE/src/test/resources/payload/get-token-response.json @@ -0,0 +1,7 @@ +{ + "token_type":"bearer", + "access_token":"accessToken", + "expires_in":43199, + "refresh_token":"refreshToken", + "refresh_token_expires_in":25184000 +}