From 3d452956a0bbefd0ff904b23ae3e6b97eb0a0816 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Fri, 8 Nov 2024 14:16:06 +0900 Subject: [PATCH 01/35] =?UTF-8?q?#107=20[style]=20:=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=EC=9A=A9=20CICD=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=9D=84=20=EB=B3=80=EA=B2=BD=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/{cicd.yml => dev-cicd.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{cicd.yml => dev-cicd.yml} (98%) diff --git a/.github/workflows/cicd.yml b/.github/workflows/dev-cicd.yml similarity index 98% rename from .github/workflows/cicd.yml rename to .github/workflows/dev-cicd.yml index b6ed4fe..64351b7 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/dev-cicd.yml @@ -1,4 +1,4 @@ -name: Onetime CI/CD with Gradle +name: Onetime DEV CI/CD with Gradle on: push: From 54372af8fcd62ea9eb289a30cc00ab41b7a9112a Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Fri, 8 Nov 2024 14:17:41 +0900 Subject: [PATCH 02/35] =?UTF-8?q?#107=20[feat]=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=84=9C=EB=B2=84=EC=9A=A9=20CICD=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=EB=A5=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-cicd.yml | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/test-cicd.yml diff --git a/.github/workflows/test-cicd.yml b/.github/workflows/test-cicd.yml new file mode 100644 index 0000000..afbecbd --- /dev/null +++ b/.github/workflows/test-cicd.yml @@ -0,0 +1,61 @@ +name: Onetime TEST CI/CD with Gradle + +on: + push: + branches: [ "test" ] + pull_request: + branches: [ "test" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: ⏱️Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: ⏱️Gradle Caching - 빌드 시간 향상 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: ⏱️gradle build를 위한 권한을 부여합니다. + run: chmod +x ./gradlew + + - name: ⏱️gradle build 중입니다. + run: ./gradlew build -x test + + - name: ⏱️Docker Hub에 로그인합니다. + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: ⏱️Docker image build 후 Docker hub에 push합니다. + run: | + docker build -f Dockerfile -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKER_IMAGE_NAME }} . + docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKER_IMAGE_NAME }} + + - name: ⏱️Docker hub에서 pull 후 deploy합니다. + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.GCP_TEST_SERVER_HOST }} + username: ${{ secrets.GCP_TEST_SERVER_USERNAME }} + key: ${{ secrets.GCP_SSH_KEY }} + script: | + sudo chmod 777 ./deploy.sh + ./deploy.sh + sudo docker image prune -f \ No newline at end of file From 1191cd75f77c93557cd6d9d1fccd034fec3d68a7 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 13:51:55 +0900 Subject: [PATCH 03/35] =?UTF-8?q?#112=20[chore]=20:=20REST=20Docs=20&=20Sw?= =?UTF-8?q?agger=20=EA=B4=80=EB=A0=A8=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 05639e9..379998c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,11 @@ plugins { id 'java' id 'org.springframework.boot' version '3.3.2' id 'io.spring.dependency-management' version '1.1.6' + + // REST Docs + id "org.asciidoctor.jvm.convert" version "3.3.2" + id 'com.epages.restdocs-api-spec' version '0.19.2' + id 'org.hidetake.swagger.generator' version '2.18.2' } group = 'side' @@ -50,10 +55,75 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.12.2' implementation 'io.jsonwebtoken:jjwt-impl:0.12.2' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.2' + // REST Docs & Swagger + testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.19.2' + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc:3.0.0' + testImplementation 'com.squareup.okhttp3:mockwebserver' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' +} + +// QueryDSL 디렉토리 +def querydslDir = "src/main/generated" + +// REST Docs & Swagger 설정 +ext { + snippetsDir = file('build/generated-snippets') } tasks.named('test') { useJUnitPlatform() + outputs.dir snippetsDir +} + +sourceSets { + test { + java { + srcDirs = ['src/test/java'] + } + } +} + +def serverUrl = "https://onetime-test.store" + +openapi3 { + server = serverUrl + title = "OneTime API Documentation" + description = "Spring REST Docs with Swagger UI." + version = "0.0.1" + outputFileNamePrefix = 'open-api-3.0.1' + format = 'json' + outputDirectory = 'build/resources/main/static/docs' +} + +tasks.withType(GenerateSwaggerUI).configureEach { + dependsOn 'openapi3' + + delete file('src/main/resources/static/docs/') + copy { + from "build/resources/main/static/docs" + into "src/main/resources/static/docs/" + } +} + +tasks.named('asciidoctor') { + inputs.dir snippetsDir + dependsOn test +} + +tasks.named("bootJar") { + dependsOn asciidoctor + from("${asciidoctor.outputDir}") { + into 'static/docs' + } + dependsOn ':openapi3' +} + +tasks.register('copyDocument', Copy) { + dependsOn asciidoctor + from file(project.layout.buildDirectory.dir("docs/asciidoc").get().asFile.path) + into file("src/main/resources/static/docs") } -def querydslDir = "src/main/generated" \ No newline at end of file +tasks.named("build") { + dependsOn copyDocument +} \ No newline at end of file From cc0a2fc5030a14ed3a4b7cb9cffac9ca07903929 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 13:52:17 +0900 Subject: [PATCH 04/35] =?UTF-8?q?#112=20[feat]=20:=20SwaggerConfig?= =?UTF-8?q?=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/common/status/ErrorStatus.java | 1 + .../onetime/global/config/SwaggerConfig.java | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/main/java/side/onetime/global/config/SwaggerConfig.java diff --git a/src/main/java/side/onetime/global/common/status/ErrorStatus.java b/src/main/java/side/onetime/global/common/status/ErrorStatus.java index 5f0b9f8..0c3a7a0 100644 --- a/src/main/java/side/onetime/global/common/status/ErrorStatus.java +++ b/src/main/java/side/onetime/global/common/status/ErrorStatus.java @@ -17,6 +17,7 @@ public enum ErrorStatus implements BaseErrorCode { _METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "405", "허용되지 않은 요청 메소드입니다."), _UNSUPPORTED_MEDIA_TYPE(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "415", "지원되지 않는 미디어 타입입니다."), _NOT_FOUND_HANDLER(HttpStatus.NOT_FOUND, "404", "해당 경로에 대한 핸들러를 찾을 수 없습니다."), + _FAILED_TRANSLATE_SWAGGER(HttpStatus.INTERNAL_SERVER_ERROR, "500", "Rest Docs로 생성된 json파일을 통한 스웨거 변환에 실패하였습니다.") ; private final HttpStatus httpStatus; diff --git a/src/main/java/side/onetime/global/config/SwaggerConfig.java b/src/main/java/side/onetime/global/config/SwaggerConfig.java new file mode 100644 index 0000000..2549fb5 --- /dev/null +++ b/src/main/java/side/onetime/global/config/SwaggerConfig.java @@ -0,0 +1,51 @@ +package side.onetime.global.config; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import side.onetime.exception.CustomException; +import side.onetime.global.common.status.ErrorStatus; + +import java.io.InputStream; +import java.util.List; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI customOpenAPI() { + OpenAPI openAPI = new OpenAPI() + .info(new Info().title("OneTime API Documentation").version("0.0.1").description("Spring REST Docs with Swagger UI.")) + .servers(List.of( + new Server().url("http://localhost:8090").description("로컬 서버"), + new Server().url("https://onetime-test.store").description("테스트 서버") + )); + + // REST Docs에서 생성한 open-api-3.0.1.json 파일 읽어오기 + try { + ClassPathResource resource = new ClassPathResource("static/docs/open-api-3.0.1.json"); + InputStream inputStream = resource.getInputStream(); + ObjectMapper mapper = new ObjectMapper(); + + // open-api-3.0.1.json 파일을 OpenAPI 객체로 매핑 + OpenAPI restDocsOpenAPI = mapper.readValue(inputStream, OpenAPI.class); + + // REST Docs에서 생성한 Paths 정보 병합 + Paths paths = restDocsOpenAPI.getPaths(); + openAPI.setPaths(paths); + + openAPI.components(restDocsOpenAPI.getComponents()); + + } catch (Exception e) { + throw new CustomException(ErrorStatus._FAILED_TRANSLATE_SWAGGER); + } + + return openAPI; + } +} \ No newline at end of file From 751b27ee545c17473042a5ce8e4ea314ad2fac3b Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 14:00:08 +0900 Subject: [PATCH 05/35] =?UTF-8?q?#112=20[chore]=20:=20GitHub=20Actions=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EA=B3=BC=EC=A0=95=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev-cicd.yml | 2 +- .github/workflows/test-cicd.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev-cicd.yml b/.github/workflows/dev-cicd.yml index 64351b7..561ded5 100644 --- a/.github/workflows/dev-cicd.yml +++ b/.github/workflows/dev-cicd.yml @@ -36,7 +36,7 @@ jobs: run: chmod +x ./gradlew - name: ⏱️gradle build 중입니다. - run: ./gradlew build -x test + run: ./gradlew clean build openapi3 asciidoctor - name: ⏱️Docker Hub에 로그인합니다. uses: docker/login-action@v2 diff --git a/.github/workflows/test-cicd.yml b/.github/workflows/test-cicd.yml index afbecbd..9990e24 100644 --- a/.github/workflows/test-cicd.yml +++ b/.github/workflows/test-cicd.yml @@ -36,7 +36,7 @@ jobs: run: chmod +x ./gradlew - name: ⏱️gradle build 중입니다. - run: ./gradlew build -x test + run: ./gradlew clean build openapi3 asciidoctor - name: ⏱️Docker Hub에 로그인합니다. uses: docker/login-action@v2 From 34c9414bb68bfc3228c7a0288902f47e0cb09aaf Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 14:00:22 +0900 Subject: [PATCH 06/35] =?UTF-8?q?#112=20[docs]=20:=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/static/docs/open-api-3.0.1.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/resources/static/docs/open-api-3.0.1.json diff --git a/src/main/resources/static/docs/open-api-3.0.1.json b/src/main/resources/static/docs/open-api-3.0.1.json new file mode 100644 index 0000000..9edf093 --- /dev/null +++ b/src/main/resources/static/docs/open-api-3.0.1.json @@ -0,0 +1,16 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "OneTime API Documentation", + "description" : "Spring REST Docs with Swagger UI.", + "version" : "0.0.1" + }, + "servers" : [ { + "url" : "https://onetime-test.store" + } ], + "tags" : [ ], + "paths" : { }, + "components" : { + "schemas" : { } + } +} \ No newline at end of file From 8827c2df80180c482eb2118a16cfaf095ac38f6b Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 15:49:26 +0900 Subject: [PATCH 07/35] =?UTF-8?q?#114=20[feat]=20:=20Swagger=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=8B=9C=20=ED=86=A0=ED=81=B0=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../side/onetime/global/config/SwaggerConfig.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/side/onetime/global/config/SwaggerConfig.java b/src/main/java/side/onetime/global/config/SwaggerConfig.java index 2549fb5..d0b35e6 100644 --- a/src/main/java/side/onetime/global/config/SwaggerConfig.java +++ b/src/main/java/side/onetime/global/config/SwaggerConfig.java @@ -2,9 +2,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Paths; import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -46,6 +49,17 @@ public OpenAPI customOpenAPI() { throw new CustomException(ErrorStatus._FAILED_TRANSLATE_SWAGGER); } + // 액세스 토큰 + SecurityScheme apiKey = new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name("Authorization"); + SecurityRequirement securityRequirement = new SecurityRequirement() + .addList("Bearer Token"); + + openAPI.components(new Components().addSecuritySchemes("Bearer Token", apiKey)) + .addSecurityItem(securityRequirement); + return openAPI; } } \ No newline at end of file From d1ff086bdf31cfb098d37fd125cf69de092bd2e5 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 15:52:46 +0900 Subject: [PATCH 08/35] =?UTF-8?q?#114=20[style]=20:=20API=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=EC=9D=84=20=EB=AA=85=EC=8B=9C=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/side/onetime/global/config/SwaggerConfig.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/side/onetime/global/config/SwaggerConfig.java b/src/main/java/side/onetime/global/config/SwaggerConfig.java index d0b35e6..b28bdcc 100644 --- a/src/main/java/side/onetime/global/config/SwaggerConfig.java +++ b/src/main/java/side/onetime/global/config/SwaggerConfig.java @@ -1,7 +1,6 @@ package side.onetime.global.config; import com.fasterxml.jackson.databind.ObjectMapper; - import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Paths; @@ -24,7 +23,10 @@ public class SwaggerConfig { @Bean public OpenAPI customOpenAPI() { OpenAPI openAPI = new OpenAPI() - .info(new Info().title("OneTime API Documentation").version("0.0.1").description("Spring REST Docs with Swagger UI.")) + .info(new Info() + .title("OneTime API Documentation") + .version("1.3.0") + .description("Spring REST Docs with Swagger UI.")) .servers(List.of( new Server().url("http://localhost:8090").description("로컬 서버"), new Server().url("https://onetime-test.store").description("테스트 서버") From 05ca9648628e1dc89ea3f4bfaac87f8a68ecb795 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 19:06:52 +0900 Subject: [PATCH 09/35] =?UTF-8?q?#114=20[feat]=20:=20Contact=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/side/onetime/global/config/SwaggerConfig.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/side/onetime/global/config/SwaggerConfig.java b/src/main/java/side/onetime/global/config/SwaggerConfig.java index b28bdcc..f8de420 100644 --- a/src/main/java/side/onetime/global/config/SwaggerConfig.java +++ b/src/main/java/side/onetime/global/config/SwaggerConfig.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; @@ -26,7 +27,12 @@ public OpenAPI customOpenAPI() { .info(new Info() .title("OneTime API Documentation") .version("1.3.0") - .description("Spring REST Docs with Swagger UI.")) + .description("Spring REST Docs with Swagger UI.") + .contact(new Contact() + .name("Sangho Han") + .url("https://github.com/bbbang105") + .email("hchsa77@gmail.com")) + ) .servers(List.of( new Server().url("http://localhost:8090").description("로컬 서버"), new Server().url("https://onetime-test.store").description("테스트 서버") From fa6d4e633210b7ece3e0e7f07cffaf47714bffae Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 19:07:11 +0900 Subject: [PATCH 10/35] =?UTF-8?q?#114=20[fix]=20:=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=ED=8F=AC=ED=8A=B8=EB=A5=BC=20=EB=AA=85=EC=8B=9C=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EB=A5=BC=20=ED=95=B4=EA=B2=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 676a962..f4b22cf 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,5 +1,5 @@ server: - port: ${SERVER_PORT} + port: 8090 spring: datasource: From 77c529b4bfd92b3697268fcaa5bebccd89be76aa Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 19:07:31 +0900 Subject: [PATCH 11/35] =?UTF-8?q?#114=20[feat]=20:=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=84=A4=EC=A0=95=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/ControllerTestConfig.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/test/java/side/onetime/configuration/ControllerTestConfig.java diff --git a/src/test/java/side/onetime/configuration/ControllerTestConfig.java b/src/test/java/side/onetime/configuration/ControllerTestConfig.java new file mode 100644 index 0000000..494f4d0 --- /dev/null +++ b/src/test/java/side/onetime/configuration/ControllerTestConfig.java @@ -0,0 +1,37 @@ +package side.onetime.configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + +@AutoConfigureMockMvc +@AutoConfigureRestDocs +@ExtendWith({RestDocumentationExtension.class}) +public abstract class ControllerTestConfig { + @Autowired + protected WebApplicationContext context; + @Autowired + protected ObjectMapper objectMapper; + protected MockMvc mockMvc; + + @BeforeEach + void setUp(final RestDocumentationContextProvider restDocumentation) { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(documentationConfiguration(restDocumentation)) + .addFilters(new CharacterEncodingFilter("UTF-8", true)) + .alwaysDo(print()) + .build(); + } +} \ No newline at end of file From dd5272c071eddf18befdeb5b6312ac4aa8668a4f Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 19:08:03 +0900 Subject: [PATCH 12/35] =?UTF-8?q?#114=20[feat]=20:=20Event=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/event/EventControllerTest.java | 388 ++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 src/test/java/side/onetime/event/EventControllerTest.java diff --git a/src/test/java/side/onetime/event/EventControllerTest.java b/src/test/java/side/onetime/event/EventControllerTest.java new file mode 100644 index 0000000..e7f1d4f --- /dev/null +++ b/src/test/java/side/onetime/event/EventControllerTest.java @@ -0,0 +1,388 @@ +package side.onetime.event; + +import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper; +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.epages.restdocs.apispec.Schema; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; +import side.onetime.configuration.ControllerTestConfig; +import side.onetime.controller.EventController; +import side.onetime.domain.enums.Category; +import side.onetime.domain.enums.EventStatus; +import side.onetime.dto.event.request.CreateEventRequest; +import side.onetime.dto.event.response.*; +import side.onetime.service.EventService; +import side.onetime.util.JwtUtil; + +import java.util.List; +import java.util.UUID; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(EventController.class) +public class EventControllerTest extends ControllerTestConfig { + + @MockBean + private EventService eventService; + + @MockBean + private JwtUtil jwtUtil; + + @Test + @DisplayName("이벤트를 생성한다. (토큰 유무에 따라 로그인/비로그인 구분)") + public void createEventForAnonymousUser() throws Exception { + // given + UUID eventId = UUID.randomUUID(); + CreateEventResponse response = new CreateEventResponse(eventId); + Mockito.when(eventService.createEventForAnonymousUser(any(CreateEventRequest.class))) + .thenReturn(response); + + CreateEventRequest request = new CreateEventRequest( + "Sample Event", + "10:00", + "12:00", + Category.DATE, + List.of("2024.11.13") + ); + + String requestContent = new ObjectMapper().writeValueAsString(request); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.post("/api/v1/events") + .content(requestContent) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("201")) + .andExpect(jsonPath("$.message").value("이벤트 생성에 성공했습니다.")) + .andExpect(jsonPath("$.payload.event_id").value(eventId.toString())) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("event/create", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Event API") + .description("이벤트를 생성한다.(토큰 유무에 따라 로그인/비로그인 구분)") + .requestFields( + fieldWithPath("title").type(JsonFieldType.STRING).description("이벤트 제목"), + fieldWithPath("start_time").type(JsonFieldType.STRING).description("이벤트 시작 시간"), + fieldWithPath("end_time").type(JsonFieldType.STRING).description("이벤트 종료 시간"), + fieldWithPath("category").type(JsonFieldType.STRING).description("이벤트 카테고리"), + fieldWithPath("ranges").type(JsonFieldType.ARRAY).description("이벤트 날짜 또는 요일 범위") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload").type(JsonFieldType.OBJECT).description("응답 데이터"), + fieldWithPath("payload.event_id").type(JsonFieldType.STRING).description("생성된 이벤트의 UUID (형식: UUID)") + ) + .requestSchema(Schema.schema("CreateEventRequestSchema")) + .responseSchema(Schema.schema("CreateEventResponseSchema")) + .build() + ) + )); + } + + @Test + @DisplayName("이벤트를 조회한다.") + public void getEvent() throws Exception { + // given + UUID eventId = UUID.randomUUID(); + GetEventResponse response = new GetEventResponse( + "Sample Event", + "10:00", + "12:00", + Category.DATE, + List.of("2024.11.13"), + EventStatus.CREATOR + ); + + Mockito.when(eventService.getEvent(eventId.toString(), null)) + .thenReturn(response); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/events/{event_id}", eventId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("이벤트 조회에 성공했습니다.")) + .andExpect(jsonPath("$.payload.title").value("Sample Event")) + .andExpect(jsonPath("$.payload.start_time").value("10:00")) + .andExpect(jsonPath("$.payload.end_time").value("12:00")) + .andExpect(jsonPath("$.payload.category").value("DATE")) + .andExpect(jsonPath("$.payload.ranges[0]").value("2024.11.13")) + .andExpect(jsonPath("$.payload.event_status").value("CREATOR")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("event/get", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Event API") + .description("이벤트를 조회한다.") + .pathParameters( + parameterWithName("event_id").description("조회할 이벤트의 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload").type(JsonFieldType.OBJECT).description("응답 데이터"), + fieldWithPath("payload.title").type(JsonFieldType.STRING).description("이벤트 제목"), + fieldWithPath("payload.start_time").type(JsonFieldType.STRING).description("이벤트 시작 시간"), + fieldWithPath("payload.end_time").type(JsonFieldType.STRING).description("이벤트 종료 시간"), + fieldWithPath("payload.category").type(JsonFieldType.STRING).description("이벤트 카테고리"), + fieldWithPath("payload.ranges").type(JsonFieldType.ARRAY).description("이벤트 날짜 또는 요일 범위"), + fieldWithPath("payload.event_status").type(JsonFieldType.STRING).description("이벤트 상태 (로그인 유저만 반환)") + ) + .responseSchema(Schema.schema("GetEventResponseSchema")) + .build() + ) + )); + } + + @Test + @DisplayName("이벤트 참여자 목록을 조회한다.") + public void getParticipants() throws Exception { + // given + String eventId = UUID.randomUUID().toString(); + GetParticipantsResponse response = new GetParticipantsResponse(List.of("Member1", "User1", "Member2", "User2")); + + Mockito.when(eventService.getParticipants(anyString())) + .thenReturn(response); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/events/{event_id}/participants", eventId) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("참여자 조회에 성공했습니다.")) + .andExpect(jsonPath("$.payload.names[0]").value("Member1")) + .andExpect(jsonPath("$.payload.names[1]").value("User1")) + .andExpect(jsonPath("$.payload.names[2]").value("Member2")) + .andExpect(jsonPath("$.payload.names[3]").value("User2")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("event/get-participants", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Event API") + .description("이벤트 참여자 목록을 조회한다.") + .pathParameters( + parameterWithName("event_id").description("조회할 이벤트의 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload").type(JsonFieldType.OBJECT).description("응답 데이터"), + fieldWithPath("payload.names").type(JsonFieldType.ARRAY).description("참여자 이름 목록") + ) + .responseSchema(Schema.schema("GetParticipantsResponseSchema")) + .build() + ) + )); + } + + @Test + @DisplayName("가장 많이 되는 시간을 조회한다.") + public void getMostPossibleTime() throws Exception { + // given + String eventId = UUID.randomUUID().toString(); + List response = List.of( + new GetMostPossibleTime("2024.11.13", "10:00", "10:30", 5, List.of("User1", "User2"), List.of("User3")), + new GetMostPossibleTime("2024.11.13", "11:00", "11:30", 4, List.of("User1", "User3"), List.of("User2")) + ); + + Mockito.when(eventService.getMostPossibleTime(anyString())).thenReturn(response); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/events/{event_id}/most", eventId) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("가장 많이 되는 시간 조회에 성공했습니다.")) + .andExpect(jsonPath("$.payload[0].time_point").value("2024.11.13")) + .andExpect(jsonPath("$.payload[0].start_time").value("10:00")) + .andExpect(jsonPath("$.payload[0].end_time").value("10:30")) + .andExpect(jsonPath("$.payload[0].possible_count").value(5)) + .andExpect(jsonPath("$.payload[0].possible_names[0]").value("User1")) + .andExpect(jsonPath("$.payload[0].possible_names[1]").value("User2")) + .andExpect(jsonPath("$.payload[0].impossible_names[0]").value("User3")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("event/get-most-possible-time", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Event API") + .description("가장 많이 되는 시간을 조회한다.") + .pathParameters( + parameterWithName("event_id").description("조회할 이벤트의 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload").type(JsonFieldType.ARRAY).description("가장 많이 되는 시간 목록"), + fieldWithPath("payload[].time_point").type(JsonFieldType.STRING).description("날짜 또는 요일"), + fieldWithPath("payload[].start_time").type(JsonFieldType.STRING).description("시작 시간"), + fieldWithPath("payload[].end_time").type(JsonFieldType.STRING).description("종료 시간"), + fieldWithPath("payload[].possible_count").type(JsonFieldType.NUMBER).description("가능한 참여자 수"), + fieldWithPath("payload[].possible_names").type(JsonFieldType.ARRAY).description("가능한 참여자 이름 목록"), + fieldWithPath("payload[].impossible_names").type(JsonFieldType.ARRAY).description("참여 불가능한 이름 목록") + ) + .responseSchema(Schema.schema("GetMostPossibleTimeResponseSchema")) + .build() + ) + )); + } + + @Test + @DisplayName("유저 참여 이벤트 목록을 조회한다.") + public void getUserParticipatedEvents() throws Exception { + // given + List response = List.of( + new GetUserParticipatedEventsResponse( + UUID.randomUUID(), + Category.DATE, + "Sample Event", + "2024.11.13", + 10, + EventStatus.CREATOR, + List.of( + new GetMostPossibleTime("2024.11.13", "10:00", "10:30", 5, List.of("User1", "User2"), List.of("User3")) + ) + ) + ); + + Mockito.when(eventService.getUserParticipatedEvents(anyString())).thenReturn(response); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/events/user/all") + .header(HttpHeaders.AUTHORIZATION, "Bearer sampleToken") + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("유저 참여 이벤트 목록 조회에 성공했습니다.")) + .andExpect(jsonPath("$.payload[0].event_id").exists()) + .andExpect(jsonPath("$.payload[0].title").value("Sample Event")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("event/get-user-participated-events", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Event API") + .description("유저가 참여한 이벤트 목록을 조회한다.") + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload").type(JsonFieldType.ARRAY).description("참여 이벤트 목록"), + fieldWithPath("payload[].event_id").type(JsonFieldType.STRING).description("이벤트 ID"), + fieldWithPath("payload[].category").type(JsonFieldType.STRING).description("이벤트 카테고리"), + fieldWithPath("payload[].title").type(JsonFieldType.STRING).description("이벤트 제목"), + fieldWithPath("payload[].created_date").type(JsonFieldType.STRING).description("이벤트 생성일"), + fieldWithPath("payload[].participant_count").type(JsonFieldType.NUMBER).description("참여자 수"), + fieldWithPath("payload[].event_status").type(JsonFieldType.STRING).description("이벤트 참여 상태"), + fieldWithPath("payload[].most_possible_times").type(JsonFieldType.ARRAY).description("가장 많이 가능한 시간대"), + fieldWithPath("payload[].most_possible_times[].time_point").type(JsonFieldType.STRING).description("날짜 또는 요일"), + fieldWithPath("payload[].most_possible_times[].start_time").type(JsonFieldType.STRING).description("시작 시간"), + fieldWithPath("payload[].most_possible_times[].end_time").type(JsonFieldType.STRING).description("종료 시간"), + fieldWithPath("payload[].most_possible_times[].possible_count").type(JsonFieldType.NUMBER).description("가능한 참여자 수"), + fieldWithPath("payload[].most_possible_times[].possible_names").type(JsonFieldType.ARRAY).description("참여 가능한 유저 이름 목록"), + fieldWithPath("payload[].most_possible_times[].impossible_names").type(JsonFieldType.ARRAY).description("참여 불가능한 유저 이름 목록") + ) + .responseSchema(Schema.schema("GetUserParticipatedEventsResponseSchema")) + .build() + ) + )); + } + + @Test + @DisplayName("유저가 생성한 이벤트를 삭제한다.") + public void removeUserCreatedEvent() throws Exception { + // given + String eventId = UUID.randomUUID().toString(); + Mockito.doNothing().when(eventService).removeUserCreatedEvent(anyString(), anyString()); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/v1/events/{event_id}", eventId) + .header(HttpHeaders.AUTHORIZATION, "Bearer sampleToken") + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("유저가 생성한 이벤트 삭제에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("event/remove-user-created-event", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Event API") + .description("유저가 생성한 이벤트를 삭제한다.") + .pathParameters( + parameterWithName("event_id").description("삭제할 이벤트의 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지") + ) + .responseSchema(Schema.schema("RemoveUserCreatedEventResponseSchema")) + .build() + ) + )); + } +} From cb38234348d30577a21dba054fd12d4872c5aaca Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 19:30:13 +0900 Subject: [PATCH 13/35] =?UTF-8?q?#114=20[feat]=20:=20User=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../side/onetime/user/UserControllerTest.java | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 src/test/java/side/onetime/user/UserControllerTest.java diff --git a/src/test/java/side/onetime/user/UserControllerTest.java b/src/test/java/side/onetime/user/UserControllerTest.java new file mode 100644 index 0000000..1a5c30b --- /dev/null +++ b/src/test/java/side/onetime/user/UserControllerTest.java @@ -0,0 +1,221 @@ +package side.onetime.user; + +import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper; +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.epages.restdocs.apispec.Schema; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; +import side.onetime.configuration.ControllerTestConfig; +import side.onetime.controller.UserController; +import side.onetime.dto.user.request.OnboardUserRequest; +import side.onetime.dto.user.request.UpdateUserProfileRequest; +import side.onetime.dto.user.response.GetUserProfileResponse; +import side.onetime.dto.user.response.OnboardUserResponse; +import side.onetime.service.UserService; +import side.onetime.util.JwtUtil; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(UserController.class) +public class UserControllerTest extends ControllerTestConfig { + @MockBean + private UserService userService; + + @MockBean + private JwtUtil jwtUtil; + + @Test + @DisplayName("유저 온보딩을 진행한다.") + public void onboardUser() throws Exception { + // given + OnboardUserResponse response = new OnboardUserResponse("sampleAccessToken", "sampleRefreshToken"); + Mockito.when(userService.onboardUser(any(OnboardUserRequest.class))).thenReturn(response); + + OnboardUserRequest request = new OnboardUserRequest("sampleRegisterToken", "UserNickname"); + + String requestContent = new ObjectMapper().writeValueAsString(request); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.post("/api/v1/users/onboarding") + .content(requestContent) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("201")) + .andExpect(jsonPath("$.message").value("유저 온보딩에 성공했습니다.")) + .andExpect(jsonPath("$.payload.access_token").value("sampleAccessToken")) + .andExpect(jsonPath("$.payload.refresh_token").value("sampleRefreshToken")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("user/onboard", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("User API") + .description("유저 온보딩을 진행한다.") + .requestFields( + fieldWithPath("register_token").type(JsonFieldType.STRING).description("레지스터 토큰"), + fieldWithPath("nickname").type(JsonFieldType.STRING).description("유저 닉네임") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload").type(JsonFieldType.OBJECT).description("응답 데이터"), + fieldWithPath("payload.access_token").type(JsonFieldType.STRING).description("액세스 토큰"), + fieldWithPath("payload.refresh_token").type(JsonFieldType.STRING).description("리프레쉬 토큰") + ) + .requestSchema(Schema.schema("OnboardUserRequestSchema")) + .responseSchema(Schema.schema("OnboardUserResponseSchema")) + .build() + ) + )); + } + + @Test + @DisplayName("유저 정보를 조회한다.") + public void getUserProfile() throws Exception { + // given + String nickname = "UserNickname"; + String email = "user@example.com"; + GetUserProfileResponse response = new GetUserProfileResponse(nickname, email); + + Mockito.when(userService.getUserProfile(anyString())).thenReturn(response); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/users/profile") + .header(HttpHeaders.AUTHORIZATION, "Bearer sampleToken") + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("유저 정보 조회에 성공했습니다.")) + .andExpect(jsonPath("$.payload.nickname").value(nickname)) + .andExpect(jsonPath("$.payload.email").value(email)) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("user/get-profile", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("User API") + .description("유저 정보를 조회한다.") + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload").type(JsonFieldType.OBJECT).description("유저 정보 데이터"), + fieldWithPath("payload.nickname").type(JsonFieldType.STRING).description("유저 닉네임"), + fieldWithPath("payload.email").type(JsonFieldType.STRING).description("유저 이메일") + ) + .responseSchema(Schema.schema("GetUserProfileResponseSchema")) + .build() + ) + )); + } + + @Test + @DisplayName("유저 정보를 수정한다.") + public void updateUserProfile() throws Exception { + // given + UpdateUserProfileRequest request = new UpdateUserProfileRequest("NewNickname"); + + Mockito.doNothing().when(userService).updateUserProfile(anyString(), any(UpdateUserProfileRequest.class)); + + String requestContent = new ObjectMapper().writeValueAsString(request); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.patch("/api/v1/users/profile/action-update") + .header(HttpHeaders.AUTHORIZATION, "Bearer sampleToken") + .contentType(MediaType.APPLICATION_JSON) + .content(requestContent) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("유저 정보 수정에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("user/update-profile", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("User API") + .description("유저 정보를 수정한다.") + .requestFields( + fieldWithPath("nickname").type(JsonFieldType.STRING).description("수정할 닉네임") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지") + ) + .build() + ) + )); + } + + @Test + @DisplayName("유저가 서비스를 탈퇴한다.") + public void withdrawService() throws Exception { + // given + Mockito.doNothing().when(userService).withdrawService(anyString()); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.post("/api/v1/users/action-withdraw") + .header(HttpHeaders.AUTHORIZATION, "Bearer sampleToken") + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("유저 서비스 탈퇴에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("user/withdraw-service", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("User API") + .description("유저가 서비스를 탈퇴한다.") + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지") + ) + .build() + ) + )); + } +} From 278116c56a58cc9d9e4b520c2e5167f3008f77cd Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 19:37:23 +0900 Subject: [PATCH 14/35] =?UTF-8?q?#114=20[feat]=20:=20Url=20API=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../side/onetime/url/UrlControllerTest.java | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 src/test/java/side/onetime/url/UrlControllerTest.java diff --git a/src/test/java/side/onetime/url/UrlControllerTest.java b/src/test/java/side/onetime/url/UrlControllerTest.java new file mode 100644 index 0000000..2ef690c --- /dev/null +++ b/src/test/java/side/onetime/url/UrlControllerTest.java @@ -0,0 +1,146 @@ +package side.onetime.url; + +import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper; +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; +import side.onetime.configuration.ControllerTestConfig; +import side.onetime.controller.UrlController; +import side.onetime.dto.url.request.ConvertToOriginalUrlRequest; +import side.onetime.dto.url.request.ConvertToShortenUrlRequest; +import side.onetime.dto.url.response.ConvertToOriginalUrlResponse; +import side.onetime.dto.url.response.ConvertToShortenUrlResponse; +import side.onetime.repository.EventRepository; +import side.onetime.service.UrlService; +import side.onetime.util.JwtUtil; + +import java.util.UUID; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.mockito.ArgumentMatchers.any; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(UrlController.class) +public class UrlControllerTest extends ControllerTestConfig { + @MockBean + private UrlService urlService; + + @MockBean + private EventRepository eventRepository; + + @MockBean + private JwtUtil jwtUtil; + + @Test + @DisplayName("원본 URL을 단축 URL로 변환한다.") + public void convertToShortenUrl() throws Exception { + // given + String originalUrl = "https://example.com/event/123e4567-e89b-12d3-a456-426614174000"; + ConvertToShortenUrlRequest request = new ConvertToShortenUrlRequest(originalUrl); + String shortenUrl = "https://short.ly/abc123"; + + Mockito.when(eventRepository.existsByEventId(any(UUID.class))).thenReturn(true); + Mockito.when(urlService.convertToShortenUrl(any(ConvertToShortenUrlRequest.class))) + .thenReturn(ConvertToShortenUrlResponse.of(shortenUrl)); + + String requestContent = new ObjectMapper().writeValueAsString(request); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.post("/api/v1/urls/action-shorten") + .contentType(MediaType.APPLICATION_JSON) + .content(requestContent) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("201")) + .andExpect(jsonPath("$.message").value("단축 URL 변환에 성공했습니다.")) + .andExpect(jsonPath("$.payload.shorten_url").value(shortenUrl)) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("url/convert-to-shorten", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("URL API") + .description("원본 URL을 단축 URL로 변환한다.") + .requestFields( + fieldWithPath("original_url").type(JsonFieldType.STRING).description("단축할 원본 URL") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload").type(JsonFieldType.OBJECT).description("응답 데이터"), + fieldWithPath("payload.shorten_url").type(JsonFieldType.STRING).description("생성된 단축 URL") + ) + .build() + ) + )); + } + + @Test + @DisplayName("단축 URL을 원본 URL로 변환한다.") + public void convertToOriginalUrl() throws Exception { + // given + String shortenUrl = "https://short.ly/abc123"; + ConvertToOriginalUrlRequest request = new ConvertToOriginalUrlRequest(shortenUrl); + String originalUrl = "https://example.com/event/123e4567-e89b-12d3-a456-426614174000"; + + Mockito.when(urlService.convertToOriginalUrl(any(ConvertToOriginalUrlRequest.class))) + .thenReturn(ConvertToOriginalUrlResponse.of(originalUrl)); + Mockito.when(eventRepository.existsByEventId(any(UUID.class))).thenReturn(true); + + String requestContent = new ObjectMapper().writeValueAsString(request); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.post("/api/v1/urls/action-original") + .contentType(MediaType.APPLICATION_JSON) + .content(requestContent) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("201")) + .andExpect(jsonPath("$.message").value("원본 URL 변환에 성공했습니다.")) + .andExpect(jsonPath("$.payload.original_url").value(originalUrl)) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("url/convert-to-original", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("URL API") + .description("단축 URL을 원본 URL로 변환한다.") + .requestFields( + fieldWithPath("shorten_url").type(JsonFieldType.STRING).description("복원할 단축 URL") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload").type(JsonFieldType.OBJECT).description("응답 데이터"), + fieldWithPath("payload.original_url").type(JsonFieldType.STRING).description("복원된 원본 URL") + ) + .build() + ) + )); + } +} \ No newline at end of file From 61383deafa4f6cf8ba4d970173f87fe0d573ac97 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 19:40:59 +0900 Subject: [PATCH 15/35] =?UTF-8?q?#114=20[feat]=20:=20Token=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/token/TokenControllerTest.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/test/java/side/onetime/token/TokenControllerTest.java diff --git a/src/test/java/side/onetime/token/TokenControllerTest.java b/src/test/java/side/onetime/token/TokenControllerTest.java new file mode 100644 index 0000000..a16fe04 --- /dev/null +++ b/src/test/java/side/onetime/token/TokenControllerTest.java @@ -0,0 +1,91 @@ +package side.onetime.token; + +import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper; +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; +import side.onetime.configuration.ControllerTestConfig; +import side.onetime.controller.TokenController; +import side.onetime.dto.token.request.ReissueTokenRequest; +import side.onetime.dto.token.response.ReissueTokenResponse; +import side.onetime.service.TokenService; +import side.onetime.util.JwtUtil; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.mockito.ArgumentMatchers.any; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(TokenController.class) +public class TokenControllerTest extends ControllerTestConfig { + @MockBean + private TokenService tokenService; + + @MockBean + private JwtUtil jwtUtil; + + @Test + @DisplayName("액세스 토큰을 재발행한다.") + public void reissueTokenSuccess() throws Exception { + // given + String oldRefreshToken = "sampleOldRefreshToken"; + String newAccessToken = "newAccessToken"; + String newRefreshToken = "newRefreshToken"; + ReissueTokenResponse response = ReissueTokenResponse.of(newAccessToken, newRefreshToken); + + Mockito.when(tokenService.reissueToken(any(ReissueTokenRequest.class))) + .thenReturn(response); + + ReissueTokenRequest request = new ReissueTokenRequest(oldRefreshToken); + String requestContent = new ObjectMapper().writeValueAsString(request); + + // when + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/tokens/action-reissue") + .content(requestContent) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("201")) + .andExpect(jsonPath("$.message").value("토큰 재발행에 성공했습니다.")) + .andExpect(jsonPath("$.payload.access_token").value(newAccessToken)) + .andExpect(jsonPath("$.payload.refresh_token").value(newRefreshToken)) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("token/reissue", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Token API") + .description("액세스 토큰을 재발행한다.") + .requestFields( + fieldWithPath("refresh_token").type(JsonFieldType.STRING).description("기존 리프레쉬 토큰") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload.access_token").type(JsonFieldType.STRING).description("새로운 액세스 토큰"), + fieldWithPath("payload.refresh_token").type(JsonFieldType.STRING).description("새로운 리프레쉬 토큰") + ) + .build() + ) + )); + } +} \ No newline at end of file From 3008756248121eef9fb15f10525cf987bd9182da Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 20:29:59 +0900 Subject: [PATCH 16/35] =?UTF-8?q?#114=20[style]=20:=20=EA=B3=B5=EB=B0=B1?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/side/onetime/global/common/status/SuccessStatus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/side/onetime/global/common/status/SuccessStatus.java b/src/main/java/side/onetime/global/common/status/SuccessStatus.java index 9fefd12..c139209 100644 --- a/src/main/java/side/onetime/global/common/status/SuccessStatus.java +++ b/src/main/java/side/onetime/global/common/status/SuccessStatus.java @@ -30,7 +30,7 @@ public enum SuccessStatus implements BaseCode { _GET_MEMBER_DAY_SCHEDULES(HttpStatus.OK, "200", "개인(비로그인) 요일 스케줄 조회에 성공했습니다."), _GET_USER_DAY_SCHEDULES(HttpStatus.OK, "200", "개인(로그인) 요일 스케줄 조회에 성공했습니다."), _GET_ALL_DATE_SCHEDULES(HttpStatus.OK, "200", "전체 날짜 스케줄 조회에 성공했습니다."), - _GET_MEMBER_DATE_SCHEDULES(HttpStatus.OK, "200", "개인(비로그인)날짜 스케줄 조회에 성공했습니다."), + _GET_MEMBER_DATE_SCHEDULES(HttpStatus.OK, "200", "개인(비로그인) 날짜 스케줄 조회에 성공했습니다."), _GET_USER_DATE_SCHEDULES(HttpStatus.OK, "200", "개인(로그인) 날짜 스케줄 조회에 성공했습니다."), _GET_FILTERED_DAY_SCHEDULES(HttpStatus.OK, "200", "멤버 필터링 요일 스케줄 조회에 성공했습니다."), _GET_FILTERED_DATE_SCHEDULES(HttpStatus.OK, "200", "멤버 필터링 날짜 스케줄 조회에 성공했습니다."), From 4db2ad83382985c3b21a3b88abdc4dd17f259b6e Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 20:30:15 +0900 Subject: [PATCH 17/35] =?UTF-8?q?#114=20[feat]=20:=20Swagger=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=EB=A5=BC=20=EC=95=8C=ED=8C=8C=EB=B2=B3=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=EB=A1=9C=20=EC=A0=95=EB=A0=AC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index f4b22cf..1f64809 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -89,4 +89,13 @@ jwt: expiration-time: ${REGISTER_TOKEN_EXPIRATION_TIME} scheduling: - cron: ${CRON} \ No newline at end of file + cron: ${CRON} + +springdoc: + swagger-ui: + path: /swagger-ui.html + tags-sorter: alpha + operations-sorter: alpha + api-docs: + path: /v3/api-docs + show-actuator: true \ No newline at end of file From 0a43ddee4aff354ff5d7fd7b26fbb66a3ff492fd Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 20:30:33 +0900 Subject: [PATCH 18/35] =?UTF-8?q?#114=20[feat]=20:=20Schedule=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/ScheduleControllerTest.java | 542 ++++++++++++++++++ 1 file changed, 542 insertions(+) create mode 100644 src/test/java/side/onetime/schedule/ScheduleControllerTest.java diff --git a/src/test/java/side/onetime/schedule/ScheduleControllerTest.java b/src/test/java/side/onetime/schedule/ScheduleControllerTest.java new file mode 100644 index 0000000..465628d --- /dev/null +++ b/src/test/java/side/onetime/schedule/ScheduleControllerTest.java @@ -0,0 +1,542 @@ +package side.onetime.schedule; + +import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper; +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; +import side.onetime.configuration.ControllerTestConfig; +import side.onetime.controller.ScheduleController; +import side.onetime.dto.schedule.request.CreateDateScheduleRequest; +import side.onetime.dto.schedule.request.CreateDayScheduleRequest; +import side.onetime.dto.schedule.request.GetFilteredSchedulesRequest; +import side.onetime.dto.schedule.response.DateSchedule; +import side.onetime.dto.schedule.response.DaySchedule; +import side.onetime.dto.schedule.response.PerDateSchedulesResponse; +import side.onetime.dto.schedule.response.PerDaySchedulesResponse; +import side.onetime.service.ScheduleService; +import side.onetime.util.JwtUtil; + +import java.util.List; +import java.util.UUID; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ScheduleController.class) +public class ScheduleControllerTest extends ControllerTestConfig { + @MockBean + private ScheduleService scheduleService; + + @MockBean + private JwtUtil jwtUtil; + + @Test + @DisplayName("요일 스케줄을 등록한다. (토큰 유무에 따라 로그인/비로그인 구분)") + public void createDaySchedulesForAnonymousUser() throws Exception { + // given + String eventId = "123e4567-e89b-12d3-a456-426614174000"; + String memberId = "789e0123-e45b-67c8-d901-234567890abc"; + List daySchedules = List.of( + new DaySchedule("월", List.of("09:00", "10:00")) + ); + CreateDayScheduleRequest request = new CreateDayScheduleRequest(eventId, memberId, daySchedules); + String requestContent = new ObjectMapper().writeValueAsString(request); + + Mockito.doNothing().when(scheduleService).createDaySchedulesForAnonymousUser(any(CreateDayScheduleRequest.class)); + + // when + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/schedules/day") + .content(requestContent) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("201")) + .andExpect(jsonPath("$.message").value("요일 스케줄 등록에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("schedule/create-day-anonymous", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Schedule API") + .description("요일 스케줄을 등록한다. (비로그인의 경우에는 멤버 ID가 필수 값)") + .requestFields( + fieldWithPath("event_id").type(JsonFieldType.STRING).description("이벤트 ID"), + fieldWithPath("member_id").type(JsonFieldType.STRING).description("멤버 ID"), + fieldWithPath("schedules[].time_point").type(JsonFieldType.STRING).description("요일"), + fieldWithPath("schedules[].times[]").type(JsonFieldType.ARRAY).description("스케줄 시간 목록") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지") + ) + .build() + ) + )); + } + + @Test + @DisplayName("날짜 스케줄을 등록한다. (토큰 유무에 따라 로그인/비로그인 구분)") + public void createDateSchedulesForAuthenticatedUser() throws Exception { + // given + String eventId = "123e4567-e89b-12d3-a456-426614174000"; + String memberId = "789e0123-e45b-67c8-d901-234567890abc"; + List dateSchedules = List.of( + new DateSchedule("2024.12.01", List.of("09:00", "10:00")) + ); + CreateDateScheduleRequest request = new CreateDateScheduleRequest(eventId, memberId, dateSchedules); + String requestContent = new ObjectMapper().writeValueAsString(request); + + Mockito.doNothing().when(scheduleService).createDateSchedulesForAnonymousUser(any(CreateDateScheduleRequest.class)); + + // when + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/schedules/date") + .content(requestContent) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("201")) + .andExpect(jsonPath("$.message").value("날짜 스케줄 등록에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("schedule/create-date-authenticated", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Schedule API") + .description("날짜 스케줄을 등록한다. (비로그인의 경우에는 멤버 ID가 필수 값)") + .requestFields( + fieldWithPath("event_id").type(JsonFieldType.STRING).description("이벤트 ID"), + fieldWithPath("member_id").type(JsonFieldType.STRING).optional().description("멤버 ID (로그인 유저는 필요 없음)"), + fieldWithPath("schedules[].time_point").type(JsonFieldType.STRING).description("날짜"), + fieldWithPath("schedules[].times[]").type(JsonFieldType.ARRAY).description("스케줄 시간 목록") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지") + ) + .build() + ) + )); + } + + @Test + @DisplayName("이벤트에 대한 모든 요일 스케줄을 조회한다.") + public void getAllDaySchedules() throws Exception { + // given + String eventId = UUID.randomUUID().toString(); + List daySchedules = List.of(new DaySchedule("월", List.of("09:00", "10:00"))); + List responses = List.of(PerDaySchedulesResponse.of("Test Member", daySchedules)); + + Mockito.when(scheduleService.getAllDaySchedules(anyString())).thenReturn(responses); + + // when + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/schedules/day/{event_id}", eventId) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("전체 요일 스케줄 조회에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("schedule/get-all-day-schedules", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Schedule API") + .description("이벤트에 대한 모든 요일 스케줄을 조회한다.") + .pathParameters( + parameterWithName("event_id").description("이벤트 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload[].name").type(JsonFieldType.STRING).description("멤버 이름"), + fieldWithPath("payload[].schedules[].time_point").type(JsonFieldType.STRING).description("요일"), + fieldWithPath("payload[].schedules[].times[]").type(JsonFieldType.ARRAY).description("스케줄 시간 목록") + ) + .build() + ) + )); + } + + @Test + @DisplayName("개인 요일 스케줄을 조회한다. (비로그인 유저)") + public void getMemberDaySchedules() throws Exception { + // given + String eventId = UUID.randomUUID().toString(); + String memberId = UUID.randomUUID().toString(); + List daySchedules = List.of(new DaySchedule("화", List.of("11:00", "12:00"))); + PerDaySchedulesResponse response = PerDaySchedulesResponse.of("Test Member", daySchedules); + + Mockito.when(scheduleService.getMemberDaySchedules(anyString(), anyString())).thenReturn(response); + + // when + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/schedules/day/{event_id}/{member_id}", eventId, memberId) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("개인(비로그인) 요일 스케줄 조회에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("schedule/get-member-day-schedules", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Schedule API") + .description("개인 요일 스케줄을 조회한다. (비로그인 유저)") + .pathParameters( + parameterWithName("event_id").description("이벤트 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]"), + parameterWithName("member_id").description("멤버 ID [예시 : 789e0123-e45b-67c8-d901-234567890abc]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload.name").type(JsonFieldType.STRING).description("멤버 이름"), + fieldWithPath("payload.schedules[].time_point").type(JsonFieldType.STRING).description("요일"), + fieldWithPath("payload.schedules[].times[]").type(JsonFieldType.ARRAY).description("스케줄 시간 목록") + ) + .build() + ) + )); + } + + @Test + @DisplayName("개인 요일 스케줄을 조회한다. (로그인 유저)") + public void getUserDaySchedules() throws Exception { + // given + String eventId = UUID.randomUUID().toString(); + String authorizationHeader = "Bearer sampleAuthToken"; + List daySchedules = List.of(new DaySchedule("수", List.of("13:00", "14:00"))); + PerDaySchedulesResponse response = PerDaySchedulesResponse.of("Test User", daySchedules); + + Mockito.when(scheduleService.getUserDaySchedules(anyString(), anyString())).thenReturn(response); + + // when + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/schedules/day/{event_id}/user", eventId) + .header("Authorization", authorizationHeader) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("개인(로그인) 요일 스케줄 조회에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("schedule/get-user-day-schedules", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Schedule API") + .description("개인 요일 스케줄을 조회한다. (로그인 유저)") + .pathParameters( + parameterWithName("event_id").description("이벤트 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload.name").type(JsonFieldType.STRING).description("사용자 이름"), + fieldWithPath("payload.schedules[].time_point").type(JsonFieldType.STRING).description("요일"), + fieldWithPath("payload.schedules[].times[]").type(JsonFieldType.ARRAY).description("스케줄 시간 목록") + ) + .build() + ) + )); + } + + @Test + @DisplayName("멤버 필터링 요일 스케줄을 조회한다.") + public void getFilteredDaySchedules() throws Exception { + // given + String eventId = UUID.randomUUID().toString(); + List names = List.of("Test Member"); + GetFilteredSchedulesRequest request = new GetFilteredSchedulesRequest(eventId, names); + List daySchedules = List.of(new DaySchedule("월", List.of("09:00", "10:00"))); + List responses = List.of(PerDaySchedulesResponse.of("Test Member", daySchedules)); + + Mockito.when(scheduleService.getFilteredDaySchedules(any(GetFilteredSchedulesRequest.class))).thenReturn(responses); + + // when + String requestContent = new ObjectMapper().writeValueAsString(request); + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/schedules/day/action-filtering") + .content(requestContent) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("멤버 필터링 요일 스케줄 조회에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("schedule/get-filtered-day-schedules", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Schedule API") + .description("멤버 필터링 요일 스케줄을 조회한다.") + .requestFields( + fieldWithPath("event_id").type(JsonFieldType.STRING).description("이벤트 ID"), + fieldWithPath("names[]").type(JsonFieldType.ARRAY).description("조회할 멤버 이름 목록") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload[].name").type(JsonFieldType.STRING).description("멤버 이름"), + fieldWithPath("payload[].schedules[].time_point").type(JsonFieldType.STRING).description("요일"), + fieldWithPath("payload[].schedules[].times[]").type(JsonFieldType.ARRAY).description("스케줄 시간 목록") + ) + .build() + ) + )); + } + + @Test + @DisplayName("이벤트에 대한 모든 날짜 스케줄을 조회한다.") + public void getAllDateSchedules() throws Exception { + // given + String eventId = UUID.randomUUID().toString(); + List dateSchedules = List.of(new DateSchedule("2024-12-01", List.of("09:00", "10:00"))); + List responses = List.of(PerDateSchedulesResponse.of("Test Member", dateSchedules)); + + Mockito.when(scheduleService.getAllDateSchedules(any(String.class))).thenReturn(responses); + + // when + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/schedules/date/{event_id}", eventId) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("전체 날짜 스케줄 조회에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("schedule/get-all-date-schedules", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Schedule API") + .description("이벤트에 대한 모든 날짜 스케줄을 조회한다.") + .pathParameters( + parameterWithName("event_id").description("이벤트 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload[].name").type(JsonFieldType.STRING).description("멤버 이름"), + fieldWithPath("payload[].schedules[].time_point").type(JsonFieldType.STRING).description("날짜"), + fieldWithPath("payload[].schedules[].times[]").type(JsonFieldType.ARRAY).description("스케줄 시간 목록") + ) + .build() + ) + )); + } + + @Test + @DisplayName("개인 날짜 스케줄을 조회한다. (비로그인 유저)") + public void getMemberDateSchedules() throws Exception { + // given + String eventId = "123e4567-e89b-12d3-a456-426614174000"; + String memberId = "789e0123-e45b-67c8-d901-234567890abc"; + PerDateSchedulesResponse response = PerDateSchedulesResponse.of("memberName", List.of(new DateSchedule("2024.12.01", List.of("09:00", "10:00")))); + + Mockito.when(scheduleService.getMemberDateSchedules(eventId, memberId)).thenReturn(response); + + // when + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/schedules/date/{event_id}/{member_id}", eventId, memberId) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("개인(비로그인) 날짜 스케줄 조회에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("schedule/get-member-date", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Schedule API") + .description("개인 날짜 스케줄을 조회한다. (비로그인 유저)") + .pathParameters( + parameterWithName("event_id").description("이벤트 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]"), + parameterWithName("member_id").description("멤버 ID [예시 : 789e0123-e45b-67c8-d901-234567890abc]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload.name").type(JsonFieldType.STRING).description("멤버 이름"), + fieldWithPath("payload.schedules[].time_point").type(JsonFieldType.STRING).description("날짜"), + fieldWithPath("payload.schedules[].times[]").type(JsonFieldType.ARRAY).description("스케줄 시간 목록") + ) + .build() + ) + )); + } + + @Test + @DisplayName("개인 날짜 스케줄을 조회한다. (로그인 유저)") + public void getUserDateSchedules() throws Exception { + // given + String eventId = "123e4567-e89b-12d3-a456-426614174000"; + String authorizationHeader = "Bearer some_token"; + PerDateSchedulesResponse response = PerDateSchedulesResponse.of("userNickname", List.of(new DateSchedule("2024.12.01", List.of("09:00", "10:00")))); + + Mockito.when(scheduleService.getUserDateSchedules(eventId, authorizationHeader)).thenReturn(response); + + // when + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/schedules/date/{event_id}/user", eventId) + .header("Authorization", authorizationHeader) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("개인(로그인) 날짜 스케줄 조회에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("schedule/get-user-date", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Schedule API") + .description("개인 날짜 스케줄을 조회한다. (로그인 유저)") + .pathParameters( + parameterWithName("event_id").description("이벤트 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload.name").type(JsonFieldType.STRING).description("유저 닉네임"), + fieldWithPath("payload.schedules[].time_point").type(JsonFieldType.STRING).description("날짜"), + fieldWithPath("payload.schedules[].times[]").type(JsonFieldType.ARRAY).description("스케줄 시간 목록") + ) + .build() + ) + )); + } + + @Test + @DisplayName("멤버 필터링 날짜 스케줄을 조회한다.") + public void getFilteredDateSchedules() throws Exception { + // given + String eventId = "123e4567-e89b-12d3-a456-426614174000"; + List names = List.of("memberName1", "memberName2"); + GetFilteredSchedulesRequest request = new GetFilteredSchedulesRequest(eventId, names); + List responseList = List.of( + PerDateSchedulesResponse.of("memberName1", List.of(new DateSchedule("2024.12.01", List.of("09:00", "10:00")))), + PerDateSchedulesResponse.of("memberName2", List.of(new DateSchedule("2024.12.02", List.of("11:00", "12:00")))) + ); + + Mockito.when(scheduleService.getFilteredDateSchedules(any(GetFilteredSchedulesRequest.class))).thenReturn(responseList); + String requestContent = new ObjectMapper().writeValueAsString(request); + + // when + ResultActions resultActions = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/schedules/date/action-filtering") + .content(requestContent) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("멤버 필터링 날짜 스케줄 조회에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("schedule/get-filtered-date", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Schedule API") + .description("멤버 필터링 날짜 스케줄을 조회한다.") + .requestFields( + fieldWithPath("event_id").type(JsonFieldType.STRING).description("이벤트 ID"), + fieldWithPath("names[]").type(JsonFieldType.ARRAY).description("이름 목록") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload[].name").type(JsonFieldType.STRING).description("멤버 이름"), + fieldWithPath("payload[].schedules[].time_point").type(JsonFieldType.STRING).description("날짜"), + fieldWithPath("payload[].schedules[].times[]").type(JsonFieldType.ARRAY).description("스케줄 시간 목록") + ) + .build() + ) + )); + } +} \ No newline at end of file From f7b82f249f2dbdc3f970691032c6264ed04cbfa0 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 20:46:02 +0900 Subject: [PATCH 19/35] =?UTF-8?q?#114=20[feat]=20:=20Member=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/member/MemberControllerTest.java | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 src/test/java/side/onetime/member/MemberControllerTest.java diff --git a/src/test/java/side/onetime/member/MemberControllerTest.java b/src/test/java/side/onetime/member/MemberControllerTest.java new file mode 100644 index 0000000..307bcfd --- /dev/null +++ b/src/test/java/side/onetime/member/MemberControllerTest.java @@ -0,0 +1,201 @@ +package side.onetime.member; + +import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper; +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; +import side.onetime.configuration.ControllerTestConfig; +import side.onetime.controller.MemberController; +import side.onetime.domain.enums.Category; +import side.onetime.dto.member.request.IsDuplicateRequest; +import side.onetime.dto.member.request.LoginMemberRequest; +import side.onetime.dto.member.request.RegisterMemberRequest; +import side.onetime.dto.member.response.IsDuplicateResponse; +import side.onetime.dto.member.response.LoginMemberResponse; +import side.onetime.dto.member.response.RegisterMemberResponse; +import side.onetime.dto.member.response.ScheduleResponse; +import side.onetime.service.MemberService; +import side.onetime.util.JwtUtil; + +import java.util.List; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.mockito.ArgumentMatchers.any; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(MemberController.class) +public class MemberControllerTest extends ControllerTestConfig { + + @MockBean + private MemberService memberService; + + @MockBean + private JwtUtil jwtUtil; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("멤버를 등록한다.") + public void registerMember() throws Exception { + // given + RegisterMemberRequest request = new RegisterMemberRequest( + "123e4567-e89b-12d3-a456-426614174000", + "newMember", + "1234", + List.of( + new ScheduleResponse("2024.12.01", List.of("09:00", "10:00")), + new ScheduleResponse("2024.12.02", List.of("11:00", "12:00")) + ) + ); + RegisterMemberResponse response = new RegisterMemberResponse("789e0123-e45b-67c8-d901-234567890abc", "CATEGORY"); + + Mockito.when(memberService.registerMember(any(RegisterMemberRequest.class))).thenReturn(response); + + // when + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/members/action-register") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isCreated()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("201")) + .andExpect(jsonPath("$.message").value("멤버 등록에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("member/register", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Member API") + .description("멤버를 등록한다.") + .requestFields( + fieldWithPath("event_id").type(JsonFieldType.STRING).description("이벤트 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("멤버 이름"), + fieldWithPath("pin").type(JsonFieldType.STRING).description("멤버 PIN"), + fieldWithPath("schedules").type(JsonFieldType.ARRAY).description("스케줄 목록"), + fieldWithPath("schedules[].time_point").type(JsonFieldType.STRING).description("스케줄 날짜"), + fieldWithPath("schedules[].times").type(JsonFieldType.ARRAY).description("시간 목록") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("HTTP 상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload.member_id").type(JsonFieldType.STRING).description("멤버 ID"), + fieldWithPath("payload.category").type(JsonFieldType.STRING).description("이벤트 카테고리") + ) + .build() + ) + )); + } + + @Test + @DisplayName("멤버 로그인을 진행한다.") + public void loginMember() throws Exception { + // given + LoginMemberRequest request = new LoginMemberRequest( + "123e4567-e89b-12d3-a456-426614174000", + "existingMember", + "1234" + ); + LoginMemberResponse response = new LoginMemberResponse("789e0123-e45b-67c8-d901-234567890abc", "DATE"); + + Mockito.when(memberService.loginMember(any(LoginMemberRequest.class))).thenReturn(response); + + // when + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/members/action-login") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("멤버 로그인에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("member/login", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Member API") + .description("멤버 로그인을 진행한다.") + .requestFields( + fieldWithPath("event_id").type(JsonFieldType.STRING).description("이벤트 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("멤버 이름"), + fieldWithPath("pin").type(JsonFieldType.STRING).description("멤버 PIN") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("HTTP 상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload.member_id").type(JsonFieldType.STRING).description("멤버 ID"), + fieldWithPath("payload.category").type(JsonFieldType.STRING).description("이벤트 카테고리") + ) + .build() + ) + )); + } + + @Test + @DisplayName("멤버 이름 중복 확인을 진행한다.") + public void isDuplicate() throws Exception { + // given + IsDuplicateRequest request = new IsDuplicateRequest( + "123e4567-e89b-12d3-a456-426614174000", + "duplicateCheckName" + ); + IsDuplicateResponse response = new IsDuplicateResponse(true); + + Mockito.when(memberService.isDuplicate(any(IsDuplicateRequest.class))).thenReturn(response); + + // when + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/members/name/action-check") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("멤버 이름 중복 확인에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("member/check-duplicate", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Member API") + .description("멤버 이름 중복 확인을 진행한다.") + .requestFields( + fieldWithPath("event_id").type(JsonFieldType.STRING).description("이벤트 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("확인할 멤버 이름") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("HTTP 상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload.is_possible").type(JsonFieldType.BOOLEAN).description("이름 사용 가능 여부") + ) + .build() + ) + )); + } +} \ No newline at end of file From 44a3fdf5ce6772561e868fbd967bbe0dcedf6513 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 13 Nov 2024 21:47:27 +0900 Subject: [PATCH 20/35] =?UTF-8?q?#114=20[feat]=20:=20Fixed=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/fixed/FixedControllerTest.java | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 src/test/java/side/onetime/fixed/FixedControllerTest.java diff --git a/src/test/java/side/onetime/fixed/FixedControllerTest.java b/src/test/java/side/onetime/fixed/FixedControllerTest.java new file mode 100644 index 0000000..9f50398 --- /dev/null +++ b/src/test/java/side/onetime/fixed/FixedControllerTest.java @@ -0,0 +1,309 @@ +package side.onetime.fixed; + +import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper; +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; +import side.onetime.configuration.ControllerTestConfig; +import side.onetime.controller.FixedController; +import side.onetime.dto.fixed.request.CreateFixedEventRequest; +import side.onetime.dto.fixed.request.ModifyFixedEventRequest; +import side.onetime.dto.fixed.response.FixedEventByDayResponse; +import side.onetime.dto.fixed.response.FixedEventDetailResponse; +import side.onetime.dto.fixed.response.FixedEventResponse; +import side.onetime.dto.fixed.response.FixedScheduleResponse; +import side.onetime.service.FixedEventService; +import side.onetime.service.FixedScheduleService; +import side.onetime.util.JwtUtil; + +import java.util.List; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(FixedController.class) +public class FixedControllerTest extends ControllerTestConfig { + + @MockBean + private FixedEventService fixedEventService; + + @MockBean + private FixedScheduleService fixedScheduleService; + + @MockBean + private JwtUtil jwtUtil; + + private final String authorizationHeader = "Bearer token"; + + @Test + @DisplayName("고정 스케줄을 등록한다.") + public void createFixedEvent() throws Exception { + // given + CreateFixedEventRequest request = new CreateFixedEventRequest("고정 이벤트", List.of(new FixedScheduleResponse("월", List.of("09:00", "09:30")))); + + // when + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/fixed-schedules") + .header("Authorization", authorizationHeader) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isCreated()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("201")) + .andExpect(jsonPath("$.message").value("고정 스케줄 등록에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("fixed/create", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Fixed API") + .description("고정 스케줄을 등록한다.") + .requestFields( + fieldWithPath("title").type(JsonFieldType.STRING).description("스케줄 이름"), + fieldWithPath("schedules").type(JsonFieldType.ARRAY).description("고정 스케줄 목록"), + fieldWithPath("schedules[].time_point").type(JsonFieldType.STRING).description("요일"), + fieldWithPath("schedules[].times").type(JsonFieldType.ARRAY).description("시간 목록") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("HTTP 상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지") + ) + .build() + ) + )); + } + + @Test + @DisplayName("전체 고정 스케줄을 조회한다.") + public void getAllFixedSchedules() throws Exception { + // given + List responses = List.of( + new FixedEventResponse(1L, "09:00", "10:00", List.of(new FixedScheduleResponse("월", List.of("09:00", "09:30")))), + new FixedEventResponse(2L, "09:00", "10:00", List.of(new FixedScheduleResponse("화", List.of("09:00", "09:30")))) + ); + Mockito.when(fixedScheduleService.getAllFixedSchedules(authorizationHeader)).thenReturn(responses); + + // when + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/fixed-schedules") + .header("Authorization", authorizationHeader) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("전체 고정 스케줄 조회에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("fixed/getAll", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Fixed API") + .description("전체 고정 스케줄을 조회한다.") + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("HTTP 상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload[].id").type(JsonFieldType.NUMBER).description("고정 스케줄 ID"), + fieldWithPath("payload[].start_time").type(JsonFieldType.STRING).description("시작 시간"), + fieldWithPath("payload[].end_time").type(JsonFieldType.STRING).description("종료 시간"), + fieldWithPath("payload[].schedules[].time_point").type(JsonFieldType.STRING).description("요일"), + fieldWithPath("payload[].schedules[].times[]").type(JsonFieldType.ARRAY).description("시간 목록") + ) + .build() + ) + )); + } + + @Test + @DisplayName("특정 고정 스케줄을 상세 조회한다.") + public void getFixedScheduleDetail() throws Exception { + // given + Long fixedEventId = 1L; + FixedEventDetailResponse response = new FixedEventDetailResponse("고정 이벤트", "09:00", "10:00", List.of(new FixedScheduleResponse("월", List.of("09:00", "09:30")))); + Mockito.when(fixedScheduleService.getFixedScheduleDetail(authorizationHeader, fixedEventId)).thenReturn(response); + + // when + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/fixed-schedules/{id}", fixedEventId) + .header("Authorization", authorizationHeader) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("특정 고정 스케줄 상세 조회에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("fixed/getDetail", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Fixed API") + .description("특정 고정 스케줄을 상세 조회한다.") + .pathParameters( + parameterWithName("id").description("고정 스케줄 ID [예시 : 1 (NUMBER Type)]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("HTTP 상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload.title").type(JsonFieldType.STRING).description("고정 스케줄 제목"), + fieldWithPath("payload.start_time").type(JsonFieldType.STRING).description("시작 시간"), + fieldWithPath("payload.end_time").type(JsonFieldType.STRING).description("종료 시간"), + fieldWithPath("payload.schedules[].time_point").type(JsonFieldType.STRING).description("요일"), + fieldWithPath("payload.schedules[].times[]").type(JsonFieldType.ARRAY).description("시간 목록") + ) + .build() + ) + )); + } + + @Test + @DisplayName("특정 고정 스케줄을 수정한다.") + public void modifyFixedEvent() throws Exception { + // given + Long fixedEventId = 1L; + ModifyFixedEventRequest request = new ModifyFixedEventRequest("수정된 고정 스케줄", List.of(new FixedScheduleResponse("화", List.of("10:00", "11:00")))); + + // when + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.patch("/api/v1/fixed-schedules/{id}", fixedEventId) + .header("Authorization", authorizationHeader) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("고정 스케줄 수정에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("fixed/modify", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Fixed API") + .description("특정 고정 스케줄을 수정한다.") + .pathParameters( + parameterWithName("id").description("고정 스케줄 ID [예시 : 1 (NUMBER Type)]") + ) + .requestFields( + fieldWithPath("title").type(JsonFieldType.STRING).description("수정된 스케줄 이름"), + fieldWithPath("schedules").type(JsonFieldType.ARRAY).description("수정된 고정 스케줄 목록"), + fieldWithPath("schedules[].time_point").type(JsonFieldType.STRING).description("요일"), + fieldWithPath("schedules[].times").type(JsonFieldType.ARRAY).description("시간 목록") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("HTTP 상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지") + ) + .build() + ) + )); + } + + @Test + @DisplayName("특정 고정 스케줄을 삭제한다.") + public void removeFixedEvent() throws Exception { + // given + Long fixedEventId = 1L; + + // when + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.delete("/api/v1/fixed-schedules/{id}", fixedEventId) + .header("Authorization", authorizationHeader) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("고정 스케줄 삭제에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("fixed/delete", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Fixed API") + .description("특정 고정 스케줄을 삭제한다.") + .pathParameters( + parameterWithName("id").description("고정 스케줄 ID [예시 : 1 (NUMBER Type)]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("HTTP 상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지") + ) + .build() + ) + )); + } + + @Test + @DisplayName("요일별 고정 스케줄을 조회한다.") + public void getFixedEventByDay() throws Exception { + // given + String day = "mon"; + List responses = List.of(new FixedEventByDayResponse(1L, "고정 이벤트", "09:00", "10:00")); + Mockito.when(fixedEventService.getFixedEventByDay(authorizationHeader, day)).thenReturn(responses); + + // when + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/v1/fixed-schedules/by-day/{day}", day) + .header("Authorization", authorizationHeader) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("요일 별 고정 스케줄 조회에 성공했습니다.")) + .andDo(MockMvcRestDocumentationWrapper.document("fixed/getByDay", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Fixed API") + .description("요일별 고정 스케줄을 조회한다.") + .pathParameters( + parameterWithName("day").description("조회할 요일 [예시 : mon, tue, ...]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("HTTP 상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload[].id").type(JsonFieldType.NUMBER).description("고정 스케줄 ID"), + fieldWithPath("payload[].title").type(JsonFieldType.STRING).description("고정 스케줄 이름"), + fieldWithPath("payload[].start_time").type(JsonFieldType.STRING).description("시작 시간"), + fieldWithPath("payload[].end_time").type(JsonFieldType.STRING).description("종료 시간") + ) + .build() + ) + )); + } +} \ No newline at end of file From c61eea1c721d25b11edd4b0658f718beeb26c32b Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:10:38 +0900 Subject: [PATCH 21/35] =?UTF-8?q?#115=20[style]=20:=20Event=20API=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EC=9D=84=20=EA=B5=AC=EC=B2=B4=ED=99=94?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/controller/EventController.java | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/src/main/java/side/onetime/controller/EventController.java b/src/main/java/side/onetime/controller/EventController.java index b5f709a..921235d 100644 --- a/src/main/java/side/onetime/controller/EventController.java +++ b/src/main/java/side/onetime/controller/EventController.java @@ -18,7 +18,16 @@ public class EventController { private final EventService eventService; - // 이벤트 생성 API + /** + * 이벤트 생성 API + * + * 이 API는 새로운 이벤트를 생성합니다. 인증된 유저와 익명 유저 모두 이벤트를 생성할 수 있으며, + * 인증된 유저의 경우 추가적인 정보가 저장됩니다. + * + * @param createEventRequest 생성할 이벤트에 대한 요청 데이터 (제목, 시작/종료 시간, 카테고리, 설문 범위 등) + * @param authorizationHeader 인증된 유저의 토큰 (선택 사항) + * @return 생성된 이벤트의 ID + */ @PostMapping public ResponseEntity> createEvent( @Valid @RequestBody CreateEventRequest createEventRequest, @@ -34,7 +43,16 @@ public ResponseEntity> createEvent( return ApiResponse.onSuccess(SuccessStatus._CREATED_EVENT, createEventResponse); } - // 이벤트 조회 API + /** + * 이벤트 조회 API + * + * 이 API는 특정 이벤트의 세부 정보를 조회합니다. 이벤트의 제목, 시간, 카테고리 등의 정보를 제공하며 + * 인증된 유저일 경우 추가적인 정보가 포함될 수 있습니다. + * + * @param authorizationHeader 인증된 유저의 토큰 (선택 사항) + * @param eventId 조회할 이벤트의 ID + * @return 조회한 이벤트의 세부 정보 + */ @GetMapping("/{event_id}") public ResponseEntity> getEvent( @RequestHeader(value = "Authorization", required = false) String authorizationHeader, @@ -42,11 +60,17 @@ public ResponseEntity> getEvent( GetEventResponse getEventResponse = eventService.getEvent(eventId, authorizationHeader); - return ApiResponse.onSuccess(SuccessStatus._GET_EVENT, getEventResponse); } - // 참여자 조회 API + /** + * 참여자 조회 API + * + * 이 API는 특정 이벤트에 참여한 모든 참여자의 이름 목록을 조회합니다. + * + * @param eventId 참여자 목록을 조회할 이벤트의 ID + * @return 해당 이벤트에 참여한 참여자의 이름 목록 + */ @GetMapping("/{event_id}/participants") public ResponseEntity> getParticipants( @PathVariable("event_id") String eventId) { @@ -55,7 +79,14 @@ public ResponseEntity> getParticipants( return ApiResponse.onSuccess(SuccessStatus._GET_PARTICIPANTS, getParticipantsResponse); } - // 가장 많이 되는 시간 조회 API + /** + * 가장 많이 되는 시간 조회 API + * + * 이 API는 특정 이벤트에서 가장 많이 가능한 시간대를 조회하여, 가능 인원과 해당 시간대 정보를 제공합니다. + * + * @param eventId 조회할 이벤트의 ID + * @return 가능 인원이 많은 시간대와 관련 세부 정보 + */ @GetMapping("/{event_id}/most") public ResponseEntity>> getMostPossibleTime( @PathVariable("event_id") String eventId) { @@ -64,7 +95,14 @@ public ResponseEntity>> getMostPossibleTim return ApiResponse.onSuccess(SuccessStatus._GET_MOST_POSSIBLE_TIME, getMostPossibleTimes); } - // 유저 참여 이벤트 목록 조회 API + /** + * 유저 참여 이벤트 목록 조회 API + * + * 이 API는 인증된 유저가 참여한 모든 이벤트 목록을 조회합니다. 유저의 참여 상태, 이벤트 정보 등이 포함됩니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @return 유저가 참여한 이벤트 목록 + */ @GetMapping("/user/all") public ResponseEntity>> getUserParticipatedEvents( @RequestHeader("Authorization") String authorizationHeader) { @@ -73,7 +111,15 @@ public ResponseEntity>> getU return ApiResponse.onSuccess(SuccessStatus._GET_USER_PARTICIPATED_EVENTS, getUserParticipatedEventsResponses); } - // 유저가 생성한 이벤트 삭제 API + /** + * 유저가 생성한 이벤트 삭제 API + * + * 이 API는 인증된 유저가 생성한 특정 이벤트를 삭제합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param eventId 삭제할 이벤트의 ID + * @return 삭제 성공 여부 + */ @DeleteMapping("/{event_id}") public ResponseEntity> removeUserCreatedEvent( @RequestHeader("Authorization") String authorizationHeader, @@ -82,4 +128,4 @@ public ResponseEntity> removeUserCreatedEvent( eventService.removeUserCreatedEvent(authorizationHeader, eventId); return ApiResponse.onSuccess(SuccessStatus._REMOVE_USER_CREATED_EVENT); } -} +} \ No newline at end of file From e2e1893c8293a550b8aa18bea545a7e2e916234c Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:13:40 +0900 Subject: [PATCH 22/35] =?UTF-8?q?#115=20[style]=20:=20Fixed=20API=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EC=9D=84=20=EA=B5=AC=EC=B2=B4=ED=99=94?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/controller/FixedController.java | 67 +++++++++++++++---- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/src/main/java/side/onetime/controller/FixedController.java b/src/main/java/side/onetime/controller/FixedController.java index d71f368..152a877 100644 --- a/src/main/java/side/onetime/controller/FixedController.java +++ b/src/main/java/side/onetime/controller/FixedController.java @@ -23,39 +23,68 @@ public class FixedController { private final FixedEventService fixedEventService; private final FixedScheduleService fixedScheduleService; - // 고정 이벤트 생성 및 고정 스케줄 등록 API + /** + * 고정 이벤트 생성 및 고정 스케줄 등록 API + * + * 이 API는 새로운 고정 이벤트를 생성하고 관련된 고정 스케줄을 등록합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param createFixedEventRequest 생성할 고정 이벤트에 대한 요청 데이터 (제목, 스케줄 목록 등) + * @return 생성 성공 여부를 나타내는 메시지 + */ @PostMapping public ResponseEntity> createFixedEvent( @RequestHeader("Authorization") String authorizationHeader, @Valid @RequestBody CreateFixedEventRequest createFixedEventRequest) { fixedEventService.createFixedEvent(authorizationHeader, createFixedEventRequest); - return ApiResponse.onSuccess(SuccessStatus._CREATED_FIXED_SCHEDULE); } - // 전체 고정 스케줄 조회 API + /** + * 전체 고정 스케줄 조회 API + * + * 이 API는 유저가 등록한 모든 고정 스케줄을 조회합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @return 유저가 등록한 모든 고정 스케줄 목록 + */ @GetMapping public ResponseEntity>> getAllFixedSchedules( @RequestHeader("Authorization") String authorizationHeader) { List fixedEventResponses = fixedScheduleService.getAllFixedSchedules(authorizationHeader); - return ApiResponse.onSuccess(SuccessStatus._GET_ALL_FIXED_SCHEDULES, fixedEventResponses); } - // 특정 고정 스케줄 상세 조회 API + /** + * 특정 고정 스케줄 상세 조회 API + * + * 이 API는 특정 ID에 해당하는 고정 스케줄의 상세 정보를 조회합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param fixedEventId 조회할 고정 스케줄의 ID + * @return 조회된 고정 스케줄의 세부 정보 + */ @GetMapping("/{id}") public ResponseEntity> getFixedScheduleDetail( @RequestHeader("Authorization") String authorizationHeader, @PathVariable("id") Long fixedEventId) { FixedEventDetailResponse fixedEventDetailResponse = fixedScheduleService.getFixedScheduleDetail(authorizationHeader, fixedEventId); - return ApiResponse.onSuccess(SuccessStatus._GET_FIXED_SCHEDULE_DETAIL, fixedEventDetailResponse); } - // 고정 이벤트 또는 스케줄 수정 API + /** + * 고정 이벤트 또는 스케줄 수정 API + * + * 이 API는 특정 고정 이벤트의 제목과 스케줄을 수정할 수 있습니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param fixedEventId 수정할 고정 이벤트의 ID + * @param modifyFixedEventRequest 수정할 고정 이벤트의 제목 및 스케줄 + * @return 수정 성공 여부를 나타내는 메시지 + */ @PatchMapping("/{id}") public ResponseEntity> modifyFixedEvent( @RequestHeader("Authorization") String authorizationHeader, @@ -72,25 +101,39 @@ public ResponseEntity> modifyFixedEvent( return ApiResponse.onSuccess(SuccessStatus._MODIFY_FIXED_SCHEDULE); } - // 고정 이벤트 & 스케줄 삭제 API + /** + * 고정 이벤트 & 스케줄 삭제 API + * + * 이 API는 특정 ID에 해당하는 고정 이벤트와 관련된 스케줄을 삭제합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param fixedEventId 삭제할 고정 이벤트의 ID + * @return 삭제 성공 여부를 나타내는 메시지 + */ @DeleteMapping("/{id}") public ResponseEntity> removeFixedEvent( @RequestHeader("Authorization") String authorizationHeader, @PathVariable("id") Long fixedEventId) { fixedEventService.removeFixedEvent(authorizationHeader, fixedEventId); - return ApiResponse.onSuccess(SuccessStatus._REMOVE_FIXED_SCHEDULE); } - // 요일별 고정 이벤트 조회 API + /** + * 요일별 고정 이벤트 조회 API + * + * 이 API는 특정 요일에 해당하는 고정 이벤트 목록을 조회합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param day 조회할 요일 (예: 월, 화 등) + * @return 조회된 요일의 고정 이벤트 목록 + */ @GetMapping("by-day/{day}") public ResponseEntity>> getFixedEventByDay( @RequestHeader("Authorization") String authorizationHeader, @PathVariable("day") String day) { List response = fixedEventService.getFixedEventByDay(authorizationHeader, day); - return ApiResponse.onSuccess(SuccessStatus._GET_FIXED_EVENT_BY_DAY, response); } -} +} \ No newline at end of file From bb367b768c50e9615d583822427a09c4747ab9a8 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:16:17 +0900 Subject: [PATCH 23/35] =?UTF-8?q?#115=20[style]=20:=20Member=20API=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EC=9D=84=20=EA=B5=AC=EC=B2=B4=ED=99=94?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/controller/MemberController.java | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/java/side/onetime/controller/MemberController.java b/src/main/java/side/onetime/controller/MemberController.java index 6ba7f6b..baed016 100644 --- a/src/main/java/side/onetime/controller/MemberController.java +++ b/src/main/java/side/onetime/controller/MemberController.java @@ -23,7 +23,14 @@ public class MemberController { private final MemberService memberService; - // 멤버 등록 API + /** + * 멤버 등록 API + * + * 이 API는 새로운 멤버를 등록합니다. 멤버가 속한 이벤트 ID와 이름, PIN, 스케줄 목록을 받습니다. + * + * @param registerMemberRequest 등록할 멤버 정보 (이벤트 ID, 이름, PIN, 스케줄 목록) + * @return 성공 여부와 등록된 멤버 정보 (멤버 ID, 이벤트 카테고리) + */ @PostMapping("/action-register") public ResponseEntity> registerMember( @Valid @RequestBody RegisterMemberRequest registerMemberRequest) { @@ -32,7 +39,14 @@ public ResponseEntity> registerMember( return ApiResponse.onSuccess(SuccessStatus._REGISTER_MEMBER, registerMemberResponse); } - // 멤버 로그인 API + /** + * 멤버 로그인 API + * + * 이 API는 멤버의 로그인 정보를 확인하고, 로그인에 성공한 경우 멤버의 정보를 반환합니다. + * + * @param loginMemberRequest 로그인할 멤버 정보 (이벤트 ID, 이름, PIN) + * @return 성공 여부와 로그인된 멤버 정보 (멤버 ID, 이벤트 카테고리) + */ @PostMapping("/action-login") public ResponseEntity> loginMember( @Valid @RequestBody LoginMemberRequest loginMemberRequest) { @@ -41,7 +55,14 @@ public ResponseEntity> loginMember( return ApiResponse.onSuccess(SuccessStatus._LOGIN_MEMBER, loginMemberResponse); } - // 이름 중복 확인 API + /** + * 이름 중복 확인 API + * + * 이 API는 특정 이벤트에서 지정한 이름이 중복되는지 확인합니다. + * + * @param isDuplicateRequest 중복 확인할 정보 (이벤트 ID, 확인할 이름) + * @return 성공 여부와 이름 사용 가능 여부 (isPossible 필드) + */ @PostMapping("/name/action-check") public ResponseEntity> isDuplicate( @Valid @RequestBody IsDuplicateRequest isDuplicateRequest) { @@ -49,4 +70,4 @@ public ResponseEntity> isDuplicate( IsDuplicateResponse isDuplicateResponse = memberService.isDuplicate(isDuplicateRequest); return ApiResponse.onSuccess(SuccessStatus._IS_POSSIBLE_NAME, isDuplicateResponse); } -} +} \ No newline at end of file From 5a4c51f4e4dba7a76b7a4b1cd6818bc3c7718e99 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:20:35 +0900 Subject: [PATCH 24/35] =?UTF-8?q?#115=20[style]=20:=20Schedule=20API=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EC=9D=84=20=EA=B5=AC=EC=B2=B4=ED=99=94?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ScheduleController.java | 100 ++++++++++++++++-- 1 file changed, 89 insertions(+), 11 deletions(-) diff --git a/src/main/java/side/onetime/controller/ScheduleController.java b/src/main/java/side/onetime/controller/ScheduleController.java index 944aea0..27fbd49 100644 --- a/src/main/java/side/onetime/controller/ScheduleController.java +++ b/src/main/java/side/onetime/controller/ScheduleController.java @@ -21,7 +21,16 @@ public class ScheduleController { private final ScheduleService scheduleService; - // 요일 스케줄 등록 API + /** + * 요일 스케줄 등록 API + * + * 요일별 반복되는 스케줄을 등록하는 API입니다. + * 인증된 사용자와 비인증 사용자에 따라 스케줄 생성 방식이 다릅니다. + * + * @param createDayScheduleRequest 요일 스케줄 생성 요청 객체 (이벤트 ID, 멤버 ID, 요일 스케줄 목록) + * @param authorizationHeader 인증된 유저의 토큰 (선택사항) + * @return 스케줄 등록 성공 상태 + */ @PostMapping("/day") public ResponseEntity> createDaySchedules( @Valid @RequestBody CreateDayScheduleRequest createDayScheduleRequest, @@ -35,7 +44,16 @@ public ResponseEntity> createDaySchedules( return ApiResponse.onSuccess(SuccessStatus._CREATED_DAY_SCHEDULES); } - // 날짜 스케줄 등록 API + /** + * 날짜 스케줄 등록 API + * + * 특정 날짜에 대한 스케줄을 등록하는 API입니다. + * 인증된 사용자와 비인증 사용자에 따라 스케줄 생성 방식이 다릅니다. + * + * @param createDateScheduleRequest 날짜 스케줄 생성 요청 객체 (이벤트 ID, 멤버 ID, 날짜 스케줄 목록) + * @param authorizationHeader 인증된 유저의 토큰 (선택사항) + * @return 스케줄 등록 성공 상태 + */ @PostMapping("/date") public ResponseEntity> createDateSchedules( @Valid @RequestBody CreateDateScheduleRequest createDateScheduleRequest, @@ -49,7 +67,14 @@ public ResponseEntity> createDateSchedules( return ApiResponse.onSuccess(SuccessStatus._CREATED_DATE_SCHEDULES); } - // 전체 요일 스케줄 조회 API + /** + * 전체 요일 스케줄 조회 API + * + * 특정 이벤트에 등록된 모든 요일 스케줄을 조회합니다. + * + * @param eventId 조회할 이벤트의 ID + * @return 이벤트에 등록된 요일 스케줄 목록 + */ @GetMapping("/day/{event_id}") public ResponseEntity>> getAllDaySchedules( @PathVariable("event_id") String eventId) { @@ -58,7 +83,15 @@ public ResponseEntity>> getAllDaySched return ApiResponse.onSuccess(SuccessStatus._GET_ALL_DAY_SCHEDULES, perDaySchedulesResponses); } - // 개인 요일 스케줄 조회 API (비로그인) + /** + * 개인 요일 스케줄 조회 API (비로그인) + * + * 비로그인 사용자의 특정 이벤트에 대한 개인 요일 스케줄을 조회합니다. + * + * @param eventId 조회할 이벤트의 ID + * @param memberId 조회할 멤버의 ID + * @return 멤버의 요일 스케줄 + */ @GetMapping("/day/{event_id}/{member_id}") public ResponseEntity> getMemberDaySchedules( @PathVariable("event_id") String eventId, @@ -68,7 +101,15 @@ public ResponseEntity> getMemberDaySchedule return ApiResponse.onSuccess(SuccessStatus._GET_MEMBER_DAY_SCHEDULES, perDaySchedulesResponse); } - // 개인 요일 스케줄 조회 API (로그인) + /** + * 개인 요일 스케줄 조회 API (로그인) + * + * 인증된 사용자의 특정 이벤트에 대한 개인 요일 스케줄을 조회합니다. + * + * @param eventId 조회할 이벤트의 ID + * @param authorizationHeader 인증된 유저의 토큰 + * @return 사용자의 요일 스케줄 + */ @GetMapping("/day/{event_id}/user") public ResponseEntity> getUserDaySchedules( @PathVariable("event_id") String eventId, @@ -78,7 +119,14 @@ public ResponseEntity> getUserDaySchedules( return ApiResponse.onSuccess(SuccessStatus._GET_USER_DAY_SCHEDULES, perDaySchedulesResponse); } - // 멤버 필터링 요일 스케줄 조회 API + /** + * 멤버 필터링 요일 스케줄 조회 API + * + * 멤버 이름을 필터링하여 특정 이벤트의 요일 스케줄을 조회합니다. + * + * @param getFilteredSchedulesRequest 필터링할 스케줄 요청 객체 (이벤트 ID, 멤버 이름 목록) + * @return 필터링된 요일 스케줄 목록 + */ @GetMapping("/day/action-filtering") public ResponseEntity>> getFilteredDaySchedules( @Valid @RequestBody GetFilteredSchedulesRequest getFilteredSchedulesRequest) { @@ -87,7 +135,14 @@ public ResponseEntity>> getFilteredDay return ApiResponse.onSuccess(SuccessStatus._GET_FILTERED_DAY_SCHEDULES, perDaySchedulesResponses); } - // 전체 날짜 스케줄 조회 API + /** + * 전체 날짜 스케줄 조회 API + * + * 특정 이벤트에 등록된 모든 날짜 스케줄을 조회합니다. + * + * @param eventId 조회할 이벤트의 ID + * @return 이벤트에 등록된 날짜 스케줄 목록 + */ @GetMapping("/date/{event_id}") public ResponseEntity>> getAllDateSchedules( @PathVariable("event_id") String eventId) { @@ -96,7 +151,15 @@ public ResponseEntity>> getAllDateSch return ApiResponse.onSuccess(SuccessStatus._GET_ALL_DATE_SCHEDULES, perDateSchedulesResponses); } - // 개인 날짜 스케줄 조회 API (비로그인) + /** + * 개인 날짜 스케줄 조회 API (비로그인) + * + * 비로그인 사용자의 특정 이벤트에 대한 개인 날짜 스케줄을 조회합니다. + * + * @param eventId 조회할 이벤트의 ID + * @param memberId 조회할 멤버의 ID + * @return 멤버의 날짜 스케줄 + */ @GetMapping("/date/{event_id}/{member_id}") public ResponseEntity> getMemberDateSchedules( @PathVariable("event_id") String eventId, @@ -106,7 +169,15 @@ public ResponseEntity> getMemberDateSchedu return ApiResponse.onSuccess(SuccessStatus._GET_MEMBER_DATE_SCHEDULES, perDateSchedulesResponse); } - // 개인 날짜 스케줄 조회 API (로그인) + /** + * 개인 날짜 스케줄 조회 API (로그인) + * + * 인증된 사용자의 특정 이벤트에 대한 개인 날짜 스케줄을 조회합니다. + * + * @param eventId 조회할 이벤트의 ID + * @param authorizationHeader 인증된 유저의 토큰 + * @return 사용자의 날짜 스케줄 + */ @GetMapping("/date/{event_id}/user") public ResponseEntity> getUserDateSchedules( @PathVariable("event_id") String eventId, @@ -116,7 +187,14 @@ public ResponseEntity> getUserDateSchedule return ApiResponse.onSuccess(SuccessStatus._GET_USER_DATE_SCHEDULES, perDateSchedulesResponse); } - // 멤버 필터링 날짜 스케줄 조회 API + /** + * 멤버 필터링 날짜 스케줄 조회 API + * + * 멤버 이름을 필터링하여 특정 이벤트의 날짜 스케줄을 조회합니다. + * + * @param getFilteredSchedulesRequest 필터링할 스케줄 요청 객체 (이벤트 ID, 멤버 이름 목록) + * @return 필터링된 날짜 스케줄 목록 + */ @GetMapping("/date/action-filtering") public ResponseEntity>> getFilteredDateSchedules( @Valid @RequestBody GetFilteredSchedulesRequest getFilteredSchedulesRequest) { @@ -124,4 +202,4 @@ public ResponseEntity>> getFilteredDa List perDateSchedulesResponses = scheduleService.getFilteredDateSchedules(getFilteredSchedulesRequest); return ApiResponse.onSuccess(SuccessStatus._GET_FILTERED_DATE_SCHEDULES, perDateSchedulesResponses); } -} +} \ No newline at end of file From 78816cce3bba9e3480117573dfd0077ff732e399 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:20:42 +0900 Subject: [PATCH 25/35] =?UTF-8?q?#115=20[style]=20:=20Token=20API=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EC=9D=84=20=EA=B5=AC=EC=B2=B4=ED=99=94?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/side/onetime/controller/TokenController.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/side/onetime/controller/TokenController.java b/src/main/java/side/onetime/controller/TokenController.java index 471d278..f0355f4 100644 --- a/src/main/java/side/onetime/controller/TokenController.java +++ b/src/main/java/side/onetime/controller/TokenController.java @@ -19,7 +19,15 @@ public class TokenController { private final TokenService tokenService; - // 액세스 토큰 재발행 API + /** + * 액세스 토큰 재발행 API + * + * 이 API는 유효한 리프레쉬 토큰을 제공받아 새 액세스 토큰과 리프레쉬 토큰을 재발행합니다. + * 리프레쉬 토큰의 유효성을 검증하고, 인증 정보에 따라 토큰을 갱신합니다. + * + * @param reissueAccessTokenRequest 리프레쉬 토큰을 포함한 요청 객체 + * @return 재발행된 액세스 토큰과 리프레쉬 토큰을 포함하는 응답 객체 + */ @PostMapping("/action-reissue") public ResponseEntity> reissueToken( @Valid @RequestBody ReissueTokenRequest reissueAccessTokenRequest) { From 902b2046b8af64b1b3e10b57866872b6d6707f01 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:22:03 +0900 Subject: [PATCH 26/35] =?UTF-8?q?#115=20[style]=20:=20Url=20API=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EC=9D=84=20=EA=B5=AC=EC=B2=B4=ED=99=94?= =?UTF-8?q?=ED=95=98=EA=B3=A0,=20=EB=B3=80=EC=88=98=EB=AA=85=EC=9D=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/controller/UrlController.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/side/onetime/controller/UrlController.java b/src/main/java/side/onetime/controller/UrlController.java index 2425283..4ff0557 100644 --- a/src/main/java/side/onetime/controller/UrlController.java +++ b/src/main/java/side/onetime/controller/UrlController.java @@ -21,16 +21,32 @@ public class UrlController { private final UrlService urlService; - // 원본 -> 단축 URL API + /** + * 원본 URL을 단축 URL로 변환하는 API + * + * 이 API는 제공된 원본 URL을 단축 URL로 변환합니다. + * 주어진 URL에서 이벤트 ID를 추출하고, 해당 이벤트가 존재할 경우에만 단축 URL을 생성하여 반환합니다. + * + * @param convertToShortenUrlRequest 원본 URL을 포함한 요청 객체 + * @return 변환된 단축 URL을 포함하는 응답 객체 + */ @PostMapping("/action-shorten") public ResponseEntity> convertToShortenUrl( - @Valid @RequestBody ConvertToShortenUrlRequest covertToShortenUrlRequest) { + @Valid @RequestBody ConvertToShortenUrlRequest convertToShortenUrlRequest) { - ConvertToShortenUrlResponse convertToShortenUrlResponse = urlService.convertToShortenUrl(covertToShortenUrlRequest); + ConvertToShortenUrlResponse convertToShortenUrlResponse = urlService.convertToShortenUrl(convertToShortenUrlRequest); return ApiResponse.onSuccess(SuccessStatus._CONVERT_TO_SHORTEN_URL, convertToShortenUrlResponse); } - // 단축 -> 원본 URL API + /** + * 단축 URL을 원본 URL로 복원하는 API + * + * 이 API는 단축된 URL을 원래의 URL로 복원합니다. + * 복원된 URL에서 이벤트 ID를 추출하여, 해당 이벤트가 존재하는지 확인 후 원본 URL을 반환합니다. + * + * @param convertToOriginalUrlRequest 단축 URL을 포함한 요청 객체 + * @return 복원된 원본 URL을 포함하는 응답 객체 + */ @PostMapping("/action-original") public ResponseEntity> convertToOriginalUrl( @Valid @RequestBody ConvertToOriginalUrlRequest convertToOriginalUrlRequest) { @@ -38,4 +54,4 @@ public ResponseEntity> convertToOrigin ConvertToOriginalUrlResponse convertToOriginalUrlResponse = urlService.convertToOriginalUrl(convertToOriginalUrlRequest); return ApiResponse.onSuccess(SuccessStatus._CONVERT_TO_ORIGINAL_URL, convertToOriginalUrlResponse); } -} +} \ No newline at end of file From b28b37458b76eb45023eebf7a3f9678ac524ec42 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:25:04 +0900 Subject: [PATCH 27/35] =?UTF-8?q?#115=20[style]=20:=20User=20API=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EC=9D=84=20=EA=B5=AC=EC=B2=B4=ED=99=94?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/controller/UserController.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/main/java/side/onetime/controller/UserController.java b/src/main/java/side/onetime/controller/UserController.java index 2a135aa..93dad6d 100644 --- a/src/main/java/side/onetime/controller/UserController.java +++ b/src/main/java/side/onetime/controller/UserController.java @@ -18,7 +18,15 @@ public class UserController { private final UserService userService; - // 유저 온보딩 API + /** + * 유저 온보딩 API + * + * 회원가입 이후, 유저의 닉네임을 설정하고 온보딩을 완료하는 API입니다. + * 주어진 레지스터 토큰을 통해 사용자 정보를 확인한 후, 액세스 토큰과 리프레쉬 토큰을 발급하여 반환합니다. + * + * @param onboardUserRequest 유저의 레지스터 토큰과 닉네임 정보를 포함하는 요청 객체 + * @return 발급된 액세스 토큰과 리프레쉬 토큰을 포함하는 응답 객체 + */ @PostMapping("/onboarding") public ResponseEntity> onboardUser( @Valid @RequestBody OnboardUserRequest onboardUserRequest) { @@ -27,7 +35,14 @@ public ResponseEntity> onboardUser( return ApiResponse.onSuccess(SuccessStatus._ONBOARD_USER, onboardUserResponse); } - // 유저 정보 조회 API + /** + * 유저 정보 조회 API + * + * 로그인한 유저의 닉네임과 이메일 정보를 조회합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @return 유저의 닉네임과 이메일을 포함한 응답 객체 + */ @GetMapping("/profile") public ResponseEntity> getUserProfile( @RequestHeader("Authorization") String authorizationHeader) { @@ -36,7 +51,15 @@ public ResponseEntity> getUserProfile( return ApiResponse.onSuccess(SuccessStatus._GET_USER_PROFILE, getUserProfileResponse); } - // 유저 정보 수정 API + /** + * 유저 정보 수정 API + * + * 유저의 닉네임을 수정하는 API입니다. 수정된 닉네임은 최대 길이 제한을 받습니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param updateUserProfileRequest 수정할 닉네임을 포함하는 요청 객체 + * @return 성공 상태 응답 객체 + */ @PatchMapping("/profile/action-update") public ResponseEntity> updateUserProfile( @RequestHeader("Authorization") String authorizationHeader, @@ -46,7 +69,14 @@ public ResponseEntity> updateUserProfile( return ApiResponse.onSuccess(SuccessStatus._UPDATE_USER_PROFILE); } - // 유저 서비스 탈퇴 API + /** + * 유저 서비스 탈퇴 API + * + * 유저의 계정을 삭제하여 서비스에서 탈퇴하는 API입니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @return 성공 상태 응답 객체 + */ @PostMapping("/action-withdraw") public ResponseEntity> withdrawService( @RequestHeader("Authorization") String authorizationHeader) { From 89dfc14fa58db5f5c8c70c87343c77845fe3bc6f Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:54:10 +0900 Subject: [PATCH 28/35] =?UTF-8?q?#119=20[feat]=20:=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=9C=EB=AA=A9=EC=9D=84=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/side/onetime/domain/Event.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/side/onetime/domain/Event.java b/src/main/java/side/onetime/domain/Event.java index b14ed97..24793c7 100644 --- a/src/main/java/side/onetime/domain/Event.java +++ b/src/main/java/side/onetime/domain/Event.java @@ -55,4 +55,8 @@ public Event(UUID eventId, String title, String startTime, String endTime, Categ this.endTime = endTime; this.category = category; } + + public void updateTitle(String title) { + this.title = title; + } } \ No newline at end of file From ace836b37e8a0c965242d7227877074c113d5e10 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:55:04 +0900 Subject: [PATCH 29/35] =?UTF-8?q?#119=20[feat]=20:=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=EA=B0=80=20=ED=95=B4=EB=8B=B9=20=EC=9D=B4=EB=B2=A4=ED=8A=B8?= =?UTF-8?q?=EC=9D=98=20=EC=83=9D=EC=84=B1=EC=9E=90=EC=9D=B8=EC=A7=80=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/side/onetime/service/EventService.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/side/onetime/service/EventService.java b/src/main/java/side/onetime/service/EventService.java index c51fab1..cdbc667 100644 --- a/src/main/java/side/onetime/service/EventService.java +++ b/src/main/java/side/onetime/service/EventService.java @@ -7,6 +7,7 @@ import side.onetime.domain.enums.Category; import side.onetime.domain.enums.EventStatus; import side.onetime.dto.event.request.CreateEventRequest; +import side.onetime.dto.event.request.ModifyUserCreatedEventTitleRequest; import side.onetime.dto.event.response.*; import side.onetime.exception.CustomException; import side.onetime.exception.status.EventErrorStatus; @@ -305,6 +306,19 @@ public List getUserParticipatedEvents(String // 유저가 생성한 이벤트 삭제 메서드 @Transactional public void removeUserCreatedEvent(String authorizationHeader, String eventId) { + EventParticipation eventParticipation = verifyUserIsEventCreator(authorizationHeader, eventId); + eventRepository.deleteEvent(eventParticipation.getEvent()); + } + + // 유저가 생성한 이벤트 제목 수정 메서드 + @Transactional + public void modifyUserCreatedEventTitle(String authorizationHeader, String eventId, ModifyUserCreatedEventTitleRequest modifyUserCreatedEventTitleRequest) { + EventParticipation eventParticipation = verifyUserIsEventCreator(authorizationHeader, eventId); + eventParticipation.getEvent().updateTitle(modifyUserCreatedEventTitleRequest.title()); + } + + // 유저가 이벤트의 생성자인지 검증하는 메서드 + private EventParticipation verifyUserIsEventCreator(String authorizationHeader, String eventId) { User user = jwtUtil.getUserFromHeader(authorizationHeader); Event event = eventRepository.findByEventId(UUID.fromString(eventId)) .orElseThrow(() -> new CustomException(EventErrorStatus._NOT_FOUND_EVENT)); @@ -314,10 +328,9 @@ public void removeUserCreatedEvent(String authorizationHeader, String eventId) { throw new CustomException(EventParticipationErrorStatus._NOT_FOUND_EVENT_PARTICIPATION); } if (!EventStatus.CREATOR.equals(eventParticipation.getEventStatus())) { - // 해당 이벤트의 생성자가 아닌 경우 throw new CustomException(EventParticipationErrorStatus._IS_NOT_USERS_CREATED_EVENT_PARTICIPATION); } - eventRepository.deleteEvent(event); + return eventParticipation; } } \ No newline at end of file From 3122be0409c87e7a3c5cd95c2b012afeb4a16cbb Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:55:43 +0900 Subject: [PATCH 30/35] =?UTF-8?q?#119=20[feat]=20:=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=EB=8A=94=20=EC=83=9D=EC=84=B1=ED=95=9C=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=9C=EB=AA=A9=EC=9D=84=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/controller/EventController.java | 21 +++++++++++++++++++ .../ModifyUserCreatedEventTitleRequest.java | 13 ++++++++++++ .../global/common/status/SuccessStatus.java | 1 + 3 files changed, 35 insertions(+) create mode 100644 src/main/java/side/onetime/dto/event/request/ModifyUserCreatedEventTitleRequest.java diff --git a/src/main/java/side/onetime/controller/EventController.java b/src/main/java/side/onetime/controller/EventController.java index 921235d..f88ee8a 100644 --- a/src/main/java/side/onetime/controller/EventController.java +++ b/src/main/java/side/onetime/controller/EventController.java @@ -5,6 +5,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import side.onetime.dto.event.request.CreateEventRequest; +import side.onetime.dto.event.request.ModifyUserCreatedEventTitleRequest; import side.onetime.dto.event.response.*; import side.onetime.global.common.ApiResponse; import side.onetime.global.common.status.SuccessStatus; @@ -128,4 +129,24 @@ public ResponseEntity> removeUserCreatedEvent( eventService.removeUserCreatedEvent(authorizationHeader, eventId); return ApiResponse.onSuccess(SuccessStatus._REMOVE_USER_CREATED_EVENT); } + + /** + * 유저가 생성한 이벤트 제목 수정 API + * + * 이 API는 인증된 유저가 생성한 특정 이벤트의 제목을 수정합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param eventId 제목을 수정할 이벤트의 ID + * @param modifyUserCreatedEventTitleRequest 새로운 제목 정보가 담긴 요청 데이터 + * @return 수정 성공 여부 + */ + @PutMapping("/{event_id}") + public ResponseEntity> modifyUserCreatedEventTitle( + @RequestHeader("Authorization") String authorizationHeader, + @PathVariable("event_id") String eventId, + @Valid @RequestBody ModifyUserCreatedEventTitleRequest modifyUserCreatedEventTitleRequest) { + + eventService.modifyUserCreatedEventTitle(authorizationHeader, eventId, modifyUserCreatedEventTitleRequest); + return ApiResponse.onSuccess(SuccessStatus._MODIFY_USER_CREATED_EVENT_TITLE); + } } \ No newline at end of file diff --git a/src/main/java/side/onetime/dto/event/request/ModifyUserCreatedEventTitleRequest.java b/src/main/java/side/onetime/dto/event/request/ModifyUserCreatedEventTitleRequest.java new file mode 100644 index 0000000..6cf98e6 --- /dev/null +++ b/src/main/java/side/onetime/dto/event/request/ModifyUserCreatedEventTitleRequest.java @@ -0,0 +1,13 @@ +package side.onetime.dto.event.request; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotBlank; + +@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +public record ModifyUserCreatedEventTitleRequest( + @NotBlank(message = "변경할 제목은 필수 값입니다.") String title +) { +} \ No newline at end of file diff --git a/src/main/java/side/onetime/global/common/status/SuccessStatus.java b/src/main/java/side/onetime/global/common/status/SuccessStatus.java index c139209..a649526 100644 --- a/src/main/java/side/onetime/global/common/status/SuccessStatus.java +++ b/src/main/java/side/onetime/global/common/status/SuccessStatus.java @@ -19,6 +19,7 @@ public enum SuccessStatus implements BaseCode { _GET_MOST_POSSIBLE_TIME(HttpStatus.OK, "200", "가장 많이 되는 시간 조회에 성공했습니다."), _GET_USER_PARTICIPATED_EVENTS(HttpStatus.OK, "200", "유저 참여 이벤트 목록 조회에 성공했습니다."), _REMOVE_USER_CREATED_EVENT(HttpStatus.OK, "200", "유저가 생성한 이벤트 삭제에 성공했습니다."), + _MODIFY_USER_CREATED_EVENT_TITLE(HttpStatus.OK, "200", "유저가 생성한 이벤트 제목 수정에 성공했습니다."), // Member _REGISTER_MEMBER(HttpStatus.CREATED, "201", "멤버 등록에 성공했습니다."), _LOGIN_MEMBER(HttpStatus.OK, "200", "멤버 로그인에 성공했습니다."), From f080ed98c14135dbbcd0512a1d9dbcbc3e1aa304 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Thu, 14 Nov 2024 02:59:59 +0900 Subject: [PATCH 31/35] =?UTF-8?q?#119=20[feat]=20:=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=9C=EB=AA=A9=20=EC=88=98=EC=A0=95=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=ED=95=98=EC=97=AC=20=EB=AC=B8=EC=84=9C=ED=99=94?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/event/EventControllerTest.java | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/test/java/side/onetime/event/EventControllerTest.java b/src/test/java/side/onetime/event/EventControllerTest.java index e7f1d4f..0dd9f82 100644 --- a/src/test/java/side/onetime/event/EventControllerTest.java +++ b/src/test/java/side/onetime/event/EventControllerTest.java @@ -19,6 +19,7 @@ import side.onetime.domain.enums.Category; import side.onetime.domain.enums.EventStatus; import side.onetime.dto.event.request.CreateEventRequest; +import side.onetime.dto.event.request.ModifyUserCreatedEventTitleRequest; import side.onetime.dto.event.response.*; import side.onetime.service.EventService; import side.onetime.util.JwtUtil; @@ -385,4 +386,54 @@ public void removeUserCreatedEvent() throws Exception { ) )); } -} + + @Test + @DisplayName("유저가 생성한 이벤트 제목을 수정한다.") + public void modifyUserCreatedEventTitle() throws Exception { + // given + String eventId = UUID.randomUUID().toString(); + ModifyUserCreatedEventTitleRequest request = new ModifyUserCreatedEventTitleRequest("수정할 이벤트 제목"); + + String requestContent = new ObjectMapper().writeValueAsString(request); + + Mockito.doNothing().when(eventService).modifyUserCreatedEventTitle(anyString(), anyString(), any(ModifyUserCreatedEventTitleRequest.class)); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.put("/api/v1/events/{event_id}", eventId) + .header(HttpHeaders.AUTHORIZATION, "Bearer sampleToken") + .contentType(MediaType.APPLICATION_JSON) + .content(requestContent) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("유저가 생성한 이벤트 제목 수정에 성공했습니다.")) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("event/modify-user-created-event-title", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Event API") + .description("유저가 생성한 이벤트 제목을 수정한다.") + .pathParameters( + parameterWithName("event_id").description("수정할 이벤트의 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]") + ) + .requestFields( + fieldWithPath("title").type(JsonFieldType.STRING).description("새로운 이벤트 제목") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지") + ) + .responseSchema(Schema.schema("ModifyUserCreatedEventTitleResponseSchema")) + .build() + ) + )); + } +} \ No newline at end of file From 7acc869abc3f5ee69fa334285699744d18d16a19 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Mon, 18 Nov 2024 02:24:49 +0900 Subject: [PATCH 32/35] =?UTF-8?q?#121=20[fix]=20:=20Origin=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=B4=20CORS=20=EB=AC=B8=EC=A0=9C?= =?UTF-8?q?=EB=A5=BC=20=ED=95=B4=EA=B2=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/global/config/SecurityConfig.java | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/main/java/side/onetime/global/config/SecurityConfig.java b/src/main/java/side/onetime/global/config/SecurityConfig.java index be505a1..9493e9b 100644 --- a/src/main/java/side/onetime/global/config/SecurityConfig.java +++ b/src/main/java/side/onetime/global/config/SecurityConfig.java @@ -21,24 +21,37 @@ @Configuration @EnableWebSecurity public class SecurityConfig { + private final OAuthLoginSuccessHandler oAuthLoginSuccessHandler; private final OAuthLoginFailureHandler oAuthLoginFailureHandler; + private static final String[] SWAGGER_URLS = { + "/swagger-ui/**", "/v3/api-docs/**" + }; + + private static final String[] ALLOWED_ORIGINS = { + "http://localhost:5173", + "https://onetime-test.vercel.app", + "https://www.onetime-test.vercel.app", + "https://onetime-with-members.com", + "https://www.onetime-with-members.com", + "https://1-ti.me", + "https://www.1-ti.me", + "https://noonsachin.com", + "https://www.noonsachin.com", + "https://onetime-test.store.com", + "https://www.onetime-test.store.com", + }; + @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(Arrays.asList( - "http://localhost:5173", - "https://onetime-test.vercel.app", - "https://www.onetime-test.vercel.app", - "https://onetime-with-members.com", - "https://www.onetime-with-members.com", - "https://1-ti.me", - "https://www.1-ti.me" - )); + config.setAllowedOrigins(Arrays.asList(ALLOWED_ORIGINS)); config.setAllowedMethods(Collections.singletonList("*")); config.setAllowedHeaders(Collections.singletonList("*")); config.setAllowCredentials(true); + config.setExposedHeaders(Arrays.asList("Authorization", "Set-Cookie")); + config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); @@ -51,14 +64,14 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti .httpBasic(HttpBasicConfigurer::disable) .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource())) .csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests(authorize -> - authorize - .requestMatchers("/**").permitAll() + .authorizeHttpRequests(authorize -> authorize + .requestMatchers(SWAGGER_URLS).permitAll() + .requestMatchers("/**").permitAll() // 추후 변경 필요 + .anyRequest().authenticated() ) - .oauth2Login(oauth -> // OAuth2 로그인 기능에 대한 여러 설정의 진입점 - oauth - .successHandler(oAuthLoginSuccessHandler) // 로그인 성공 시 핸들러 - .failureHandler(oAuthLoginFailureHandler) // 로그인 실패 시 핸들러 + .oauth2Login(oauth -> oauth + .successHandler(oAuthLoginSuccessHandler) // OAuth 로그인 성공 핸들러 + .failureHandler(oAuthLoginFailureHandler) // OAuth 로그인 실패 핸들러 ); return httpSecurity.build(); From 547df042bc903f0ed118021eff24fefb43054cd8 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Mon, 18 Nov 2024 02:41:38 +0900 Subject: [PATCH 33/35] =?UTF-8?q?#121=20[fix]=20:=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=98=A4=ED=83=80=EB=A5=BC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/side/onetime/global/config/SecurityConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/side/onetime/global/config/SecurityConfig.java b/src/main/java/side/onetime/global/config/SecurityConfig.java index 9493e9b..586bfbf 100644 --- a/src/main/java/side/onetime/global/config/SecurityConfig.java +++ b/src/main/java/side/onetime/global/config/SecurityConfig.java @@ -39,8 +39,8 @@ public class SecurityConfig { "https://www.1-ti.me", "https://noonsachin.com", "https://www.noonsachin.com", - "https://onetime-test.store.com", - "https://www.onetime-test.store.com", + "https://onetime-test.store", + "https://www.onetime-test.store", }; @Bean From d8c0382c5b79bb559df91fbe7a3f24dff5a39310 Mon Sep 17 00:00:00 2001 From: Sangho Han <113084292+bbbang105@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:50:34 +0900 Subject: [PATCH 34/35] Update README.md --- README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 18803d0..ed99e68 100644 --- a/README.md +++ b/README.md @@ -1 +1,69 @@ -# backend +# OneTime Backend 🚀 + +## 🌐 System Architecture + +image + +## 🧱 ERD + +원타임_ERD + + +## 📄 API Documentation + +[📝 REST Docs + Swagger](https://onetime-test.store/swagger-ui/index.html#/) + +## 🔒 Rules + +### Branch + +- 생성한 이슈에 따라서 브랜치 생성 `Ex) feature/#4/login` +- `main branch` : 개발 최종 완료 시 merge +- `develop branch` : 배포 서버용 +- `test branch` : 테스트 서버용 +- `feature branch` : 각 새로운 기능 개발 +- `hotfix branch` : 배포 이후 긴급 수정 + +### **Commit Message** + +- 이슈 번호 붙여서 커밋 `Ex) #4 [feat] : 로그인 기능을 추가한다` +- Body는 추가 설명 필요하면 사용 + +| ***작업태그*** | ***내용*** | +| --- | --- | +| **feat** | 새로운 기능 추가 / 일부 코드 추가 / 일부 코드 수정 (리팩토링과 구분) / 디자인 요소 수정 | +| **fix** | 버그 수정 | +| **refactor** | 코드 리팩토링 | +| **style** | 코드 의미에 영향을 주지 않는 변경사항 (코드 포맷팅, 오타 수정, 변수명 변경, 에셋 추가) | +| **chore** | 빌드 부분 혹은 패키지 매니저 수정 사항 / 파일 이름 변경 및 위치 변경 / 파일 삭제 | +| **docs** | 문서 추가 및 수정 | +| **rename** | 패키지 혹은 폴더명, 클래스명 수정 (단독으로 시행하였을 시) | +| **remove** | 패키지 혹은 폴더, 클래스를 삭제하였을 때 (단독으로 시행하였을 시) | + +### Naming + +- **패키지명** : 한 단어 소문자 사용 `Ex) service` +- **클래스명** : 파스칼 케이스 사용 `Ex) JwtUtil` +- **메서드명** : 카멜 케이스 사용, 동사로 시작 `Ex) getUserScraps` +- **변수명** : 카멜 케이스 사용 `Ex) jwtToken` +- **상수명** : 대문자 사용 `Ex) EXPIRATION_TIME` +- **컬럼명** : 스네이크 케이스 사용 `Ex) user_id` + + +### API Response + +```json +{ + "code": "201", + "message": "이벤트 생성에 성공했습니다.", + "payload": { + "event_id": "5e35b658-ee4b-4c52-98dc-94b79f0e64c9" + }, + "is_success": true +} +``` + +- `is_success` : 성공 여부 +- `code` : 성공 코드, HTTP 상태 코드와 동일 +- `message` : 성공 메세지 +- `payload` : 데이터가 들어가는 곳 From dbf89da6eb67944c65c1403ae1d9979984fa924c Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Mon, 2 Dec 2024 00:13:17 +0900 Subject: [PATCH 35/35] =?UTF-8?q?#123=20[fix]=20:=20=EA=B0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=A7=8E=EC=9D=B4=20=EB=90=98=EB=8A=94=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EB=AC=B8=EC=A0=9C=EB=A5=BC=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/side/onetime/service/EventService.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/side/onetime/service/EventService.java b/src/main/java/side/onetime/service/EventService.java index cdbc667..639c4c0 100644 --- a/src/main/java/side/onetime/service/EventService.java +++ b/src/main/java/side/onetime/service/EventService.java @@ -229,17 +229,22 @@ private List buildMostPossibleTimes(Map mostPossibleTimes = new ArrayList<>(); GetMostPossibleTime previousTime = null; + boolean stopFlag = false; for (Map.Entry> entry : scheduleToNamesMap.entrySet()) { Schedule schedule = entry.getKey(); List curNames = entry.getValue(); if (curNames.size() == mostPossibleCnt) { - // 이전 시간대와 병합 가능한 경우 if (canMergeWithPrevious(previousTime, schedule, curNames, category)) { - // 종료 시간을 더해 업데이트 + // 이전 시간대와 병합 가능한 경우 previousTime = previousTime.updateEndTime(schedule.getTime()); - mostPossibleTimes.set(mostPossibleTimes.size() - 1, previousTime); + mostPossibleTimes.set(mostPossibleTimes.size() - 1, previousTime); // 종료 시간을 더해 업데이트 } else { + // 새로운 시간대를 추가하는 경우 + if (mostPossibleTimes.size() == MAX_MOST_POSSIBLE_TIMES_SIZE) { + // 6개를 찾았을 시 종료 + stopFlag = true; + } List impossibleNames = allMembersName.stream() .filter(name -> !curNames.contains(name)) .toList(); @@ -250,8 +255,7 @@ private List buildMostPossibleTimes(Map