From 7a32e66f476b97dfef72e0262719ae5c975c35a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:04:30 +0900 Subject: [PATCH 001/115] =?UTF-8?q?[fix]=20dev=20nginx=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-dev.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index b3c514e..a2dacd3 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -27,7 +27,6 @@ services: - mysql networks: - moplus-dev - - nginx-network mysql: image: mysql:8.0 From 8f829f88c5f0cb0d30fc0a970f821058e4956a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:09:36 +0900 Subject: [PATCH 002/115] =?UTF-8?q?[feat]=EB=AA=A8=EC=9D=98=EA=B3=A0?= =?UTF-8?q?=EC=82=AC=20=EA=B3=BC=EB=AA=A9=20=EC=B6=94=EA=B0=80=20=EA=B3=A0?= =?UTF-8?q?1,=20=EA=B3=A02?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moplus_server/domain/practiceTest/domain/Subject.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/Subject.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/Subject.java index ff6bdfa..531de9e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/Subject.java +++ b/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/Subject.java @@ -11,6 +11,8 @@ public enum Subject { // 화법과작문("화법과작문", 45, 100), // 언어와매체("언어와매체", 45, 100), + 고1("고1",30,100), + 고2("고2",30,100), 미적분("미적분", 30, 100), 확률과통계("확률과통계",30, 100), 기하("기하",30, 100); From 0aed0a1de3e2ed57d6e1b1e78e7143e7c4a3a100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:41:10 +0900 Subject: [PATCH 003/115] =?UTF-8?q?[feat]=20=EA=B3=A0,=EA=B3=A02=20?= =?UTF-8?q?=EB=B0=B0=EC=A0=90=20=EC=A0=81=EC=9A=A9=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/PracticeTestAdminService.java | 4 +- .../resources/templates/answerInputForm.html | 119 ++++++++++++------ 2 files changed, 86 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/PracticeTestAdminService.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/PracticeTestAdminService.java index e47d1cb..126c227 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/PracticeTestAdminService.java +++ b/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/PracticeTestAdminService.java @@ -79,10 +79,11 @@ public void getProblemCreateModel(Model model, Long practiceTestId) { model.addAttribute("practiceTest", practiceTest); model.addAttribute("totalQuestions", totalQuestions); model.addAttribute("hasShortAnswer", isMathPracticeTest(practiceTest)); + model.addAttribute("subject", practiceTest.getSubject().getValue()); } private static boolean isMathPracticeTest(PracticeTest practiceTest) { - return List.of("미적분", "확률과통계", "기하").contains(practiceTest.getSubject().getValue()); + return List.of("미적분", "확률과통계", "기하", "고1", "고2").contains(practiceTest.getSubject().getValue()); } @Transactional @@ -103,5 +104,6 @@ public void getProblemUpdateModel(Model model, Long practiceTestId) { model.addAttribute("practiceTest", practiceTest); model.addAttribute("totalQuestions", totalQuestions); model.addAttribute("hasShortAnswer", isMathPracticeTest(practiceTest)); + model.addAttribute("subject", practiceTest.getSubject().getValue()); } } diff --git a/src/main/resources/templates/answerInputForm.html b/src/main/resources/templates/answerInputForm.html index 1a0ad45..9ee1892 100644 --- a/src/main/resources/templates/answerInputForm.html +++ b/src/main/resources/templates/answerInputForm.html @@ -40,48 +40,92 @@

답안 입력

- - + - - - - - - - + + + + + + + + + + + + + + + + + + + + - -
- - - - - - - - - - - -
-
- - -
- + + +
+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+ + + + + + + + + + + +
+
+ + +
+ +
@@ -114,5 +158,8 @@

답안 입력

+ From 7afe07182f37c07d37bf5ff3ea2bf812c5106c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:50:06 +0900 Subject: [PATCH 004/115] =?UTF-8?q?[feat]=20=EA=B3=A0,=EA=B3=A02=20?= =?UTF-8?q?=EB=B0=B0=EC=A0=90=20=EC=A0=81=EC=9A=A9=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/templates/answerInputForm.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/answerInputForm.html b/src/main/resources/templates/answerInputForm.html index 9ee1892..c7656fa 100644 --- a/src/main/resources/templates/answerInputForm.html +++ b/src/main/resources/templates/answerInputForm.html @@ -61,10 +61,10 @@

답안 입력

th:checked="${#arrays.contains(new int[]{1, 2, 3}, i)}"> + th:checked="${#arrays.contains(new int[]{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 23, 24, 25}, i)}"> + th:checked="${#arrays.contains(new int[]{14, 15, 16, 17, 18, 19, 20, 21, 26, 27, 28, 29, 30}, i)}"> From dc37759d1d8508abd7d1d2005aa2440dc96a428c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:59:56 +0900 Subject: [PATCH 005/115] =?UTF-8?q?[fix]=20=EA=B0=9C=EB=B0=9C=20=ED=81=B4?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20CORS=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/moplus/moplus_server/global/config/WebConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java b/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java index 783451c..81074c1 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java @@ -18,7 +18,7 @@ public class WebConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("https://dev.mopl.kr","http://dev.mopl.kr", "http://localhost:8080", "https://www.mopl.kr", "http" - + "://localhost:3000") + + "://localhost:3000", "https://web-dev.mopl.kr", "http://web-dev.mopl.kr") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); From bb2bae1dbcc70667f3e31998db6b45d092008cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:02:01 +0900 Subject: [PATCH 006/115] =?UTF-8?q?[fix]=20=EA=B0=9C=EB=B0=9C=20=ED=81=B4?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20CORS=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/moplus/moplus_server/global/config/WebConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java b/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java index 81074c1..51117e6 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java @@ -18,7 +18,7 @@ public class WebConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("https://dev.mopl.kr","http://dev.mopl.kr", "http://localhost:8080", "https://www.mopl.kr", "http" - + "://localhost:3000", "https://web-dev.mopl.kr", "http://web-dev.mopl.kr") + + "://localhost:3000", "https://dev-web.mopl.kr", "http://dev-web.mopl.kr") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); From 57dda5dff5e9ba67d4444e29473293791a0bcd47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:40:28 +0900 Subject: [PATCH 007/115] =?UTF-8?q?[feat]=20elastic=20APM=EA=B3=BC=20?= =?UTF-8?q?=EC=96=B4=ED=94=8C=EC=9D=B4=EC=BC=80=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=EB=A1=9C=20=EB=AC=B6?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-dev.yml | 2 ++ docker-compose-prod.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index a2dacd3..ccaa28b 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -27,6 +27,8 @@ services: - mysql networks: - moplus-dev + - nginx-network + - apm-network mysql: image: mysql:8.0 diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index a95bf5f..052a97a 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -27,6 +27,7 @@ services: networks: - moplus-prod - nginx-network + - apm-network mysql: image: mysql:8.0 From ec9336981fff156788cd4755353b96ab0a0e1829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:43:56 +0900 Subject: [PATCH 008/115] =?UTF-8?q?[feat]=20elastic=20APM=EA=B3=BC=20?= =?UTF-8?q?=EC=96=B4=ED=94=8C=EC=9D=B4=EC=BC=80=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=EB=A1=9C=20=EB=AC=B6?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-dev.yml | 2 +- docker-compose-prod.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index ccaa28b..ac2dc05 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -28,7 +28,7 @@ services: networks: - moplus-dev - nginx-network - - apm-network + - apm_env mysql: image: mysql:8.0 diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 052a97a..92a32ea 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -27,7 +27,7 @@ services: networks: - moplus-prod - nginx-network - - apm-network + - apm_env mysql: image: mysql:8.0 From 8fae4f85be56dc09af42636fc3b3271048ddb554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:50:46 +0900 Subject: [PATCH 009/115] =?UTF-8?q?[feat]=20apm-network=20=EC=99=B8?= =?UTF-8?q?=EB=B6=80=20=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-dev.yml | 5 ++++- docker-compose-prod.yml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index ac2dc05..8070208 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -56,4 +56,7 @@ networks: moplus-dev: driver: bridge nginx-network: - external: true \ No newline at end of file + external: true + apm-network: + external: + name: apm_env \ No newline at end of file diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 92a32ea..d17f6f6 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -50,4 +50,7 @@ networks: moplus-prod: driver: bridge nginx-network: - external: true \ No newline at end of file + external: true + apm-network: + external: + name: apm_env \ No newline at end of file From 6e3114aa336ac273a5abfa96e8f3444099e758fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:56:13 +0900 Subject: [PATCH 010/115] =?UTF-8?q?[feat]=20apm-network=20=EC=99=B8?= =?UTF-8?q?=EB=B6=80=20=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-dev.yml | 5 ++--- docker-compose-prod.yml | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 8070208..821939f 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -57,6 +57,5 @@ networks: driver: bridge nginx-network: external: true - apm-network: - external: - name: apm_env \ No newline at end of file + apm_env: + external: true \ No newline at end of file diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index d17f6f6..10d3a57 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -51,6 +51,5 @@ networks: driver: bridge nginx-network: external: true - apm-network: - external: - name: apm_env \ No newline at end of file + apm_env: + external: true \ No newline at end of file From 2292fe896e73ea26bc8f4986aead895012bffb60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:59:20 +0900 Subject: [PATCH 011/115] =?UTF-8?q?[feat]=20apm=20ip=20=EC=A3=BC=EC=86=8C?= =?UTF-8?q?=20dockerfile=20=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20?= =?UTF-8?q?=EB=AA=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Dockerfile.prod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 780a42f..9609c42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ ENV SPRING_PROFILES_ACTIVE=${PROFILE} ENTRYPOINT ["java", \ "-javaagent:/apm-agent.jar", \ -"-Delastic.apm.server_urls=http://114.70.23.79:8200", \ +"-Delastic.apm.server_urls=http://apm-server:8200", \ "-Delastic.apm.service_name=moplus-apm-agent", \ "-Delastic.apm.application_packages=com.server", \ "-Delastic.apm.environment=dev", \ diff --git a/Dockerfile.prod b/Dockerfile.prod index 5f66081..c659a89 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -10,7 +10,7 @@ ENV SPRING_PROFILES_ACTIVE=${PROFILE} ENTRYPOINT ["java", \ "-javaagent:/apm-agent.jar", \ -"-Delastic.apm.server_urls=http://114.70.23.79:8200", \ +"-Delastic.apm.server_urls=http://apm-server:8200", \ "-Delastic.apm.service_name=moplus-apm-agent", \ "-Delastic.apm.application_packages=com.server", \ "-Delastic.apm.environment=prod", \ From 14e7f473b62ebb500dc695e71a404220b4265c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:32:23 +0900 Subject: [PATCH 012/115] =?UTF-8?q?[feat/#9]=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EB=B0=8F=20=EC=9D=B8=EC=A6=9D=20provider?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/CI.yml | 38 ++++++ build.gradle | 9 ++ .../MoplusServerApplication.java | 3 - .../auth/controller/AuthController.java | 39 ++++++ .../auth/dto/request/AdminLoginRequest.java | 7 ++ .../domain/member/domain/Member.java | 38 ++++++ .../domain/member/domain/MemberRole.java | 21 ++++ .../member/repository/MemberRepository.java | 9 ++ .../domain/member/service/MemberService.java | 21 ++++ .../config/properties/PropertiesConfig.java | 13 ++ .../config/security/SecurityConfig.java | 112 ++++++++++++++++++ .../global/error/exception/ErrorCode.java | 3 + .../global/properties/jwt/JwtProperties.java | 21 ++++ .../global/security/AuthConstants.java | 13 ++ .../global/security/JwtUtil.java | 55 +++++++++ .../EmailPasswordAuthenticationFilter.java | 37 ++++++ .../handler/EmailPasswordSuccessHandler.java | 29 +++++ .../EmailPasswordAuthenticationProvider.java | 43 +++++++ .../TransactionalPracticeService.java | 29 ----- src/main/resources/application-local.yml | 4 - src/main/resources/application-security.yml | 6 + src/main/resources/application.yml | 7 +- .../auth/controller/AuthControllerTest.java | 70 +++++++++++ src/test/resources/application-h2test.yml | 33 ++++++ src/test/resources/auth-test-data.sql | 4 + 25 files changed, 627 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/CI.yml create mode 100644 src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/auth/dto/request/AdminLoginRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/member/domain/MemberRole.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/member/repository/MemberRepository.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java create mode 100644 src/main/java/com/moplus/moplus_server/global/config/properties/PropertiesConfig.java create mode 100644 src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java create mode 100644 src/main/java/com/moplus/moplus_server/global/properties/jwt/JwtProperties.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/AuthConstants.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/filter/EmailPasswordAuthenticationFilter.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/provider/EmailPasswordAuthenticationProvider.java delete mode 100644 src/main/java/com/moplus/moplus_server/transactional/TransactionalPracticeService.java create mode 100644 src/main/resources/application-security.yml create mode 100644 src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java create mode 100644 src/test/resources/application-h2test.yml create mode 100644 src/test/resources/auth-test-data.sql diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..c9608e6 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,38 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created +# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle + +name: Gradle Package + +on: + pull_request: + branches: + - "master" + - "develop" + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + + - name: build and test + run: | + chmod +x gradlew + ./gradlew build diff --git a/build.gradle b/build.gradle index bdbfc70..96a2e6a 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,10 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // Spring Security + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' + //db-h2 implementation 'com.h2database:h2' testImplementation 'com.h2database:h2' @@ -44,6 +48,11 @@ dependencies { //s3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + } tasks.named('test') { diff --git a/src/main/java/com/moplus/moplus_server/MoplusServerApplication.java b/src/main/java/com/moplus/moplus_server/MoplusServerApplication.java index da5ce8f..f4f7565 100644 --- a/src/main/java/com/moplus/moplus_server/MoplusServerApplication.java +++ b/src/main/java/com/moplus/moplus_server/MoplusServerApplication.java @@ -1,14 +1,11 @@ package com.moplus.moplus_server; -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.servers.Server; import java.util.Arrays; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -@OpenAPIDefinition(servers = {@Server(url = "https://dev.mopl.kr", description = "Default Server URL")}) @SpringBootApplication @EnableJpaAuditing public class MoplusServerApplication { diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java new file mode 100644 index 0000000..9a3957e --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java @@ -0,0 +1,39 @@ +package com.moplus.moplus_server.domain.auth.controller; + +import com.moplus.moplus_server.domain.auth.dto.request.AdminLoginRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/auth") +@RequiredArgsConstructor +public class AuthController { + + @Operation(summary = "어드민 로그인", description = "아아디 패스워드 로그인 후 토큰 발급합니다.") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "로그인 성공", + headers = { + @Header(name = "Authorization", description = "Access Token", schema = @Schema(type = "string")), + @Header(name = "RefreshToken", description = "Refresh Token", schema = @Schema(type = "string")) + } + ), + @ApiResponse(responseCode = "400", description = "잘못된 요청"), + @ApiResponse(responseCode = "401", description = "인증 실패") + }) + @PostMapping("/admin/login") + public void adminLogin( + @RequestBody AdminLoginRequest request + ) { + // 실제 처리는 Security 필터에서 이루어지며, 이 메서드는 Swagger 명세용입니다. + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/dto/request/AdminLoginRequest.java b/src/main/java/com/moplus/moplus_server/domain/auth/dto/request/AdminLoginRequest.java new file mode 100644 index 0000000..861df83 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/auth/dto/request/AdminLoginRequest.java @@ -0,0 +1,7 @@ +package com.moplus.moplus_server.domain.auth.dto.request; + +public record AdminLoginRequest( + String email, + String password +) { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java b/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java new file mode 100644 index 0000000..13579cd --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java @@ -0,0 +1,38 @@ +package com.moplus.moplus_server.domain.member.domain; + +import com.moplus.moplus_server.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Member extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "member_id") + private Long id; + + private String email; + private String password; + + @Enumerated(EnumType.STRING) + private MemberRole role; + + @Builder + public Member(String nickname, String email, String password, MemberRole role) { + this.email = email; + this.password = password; + this.role = role; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/member/domain/MemberRole.java b/src/main/java/com/moplus/moplus_server/domain/member/domain/MemberRole.java new file mode 100644 index 0000000..3f38166 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/member/domain/MemberRole.java @@ -0,0 +1,21 @@ +package com.moplus.moplus_server.domain.member.domain; + +import java.util.Arrays; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum MemberRole { + USER("ROLE_USER"), + ADMIN("ROLE_ADMIN"); + + private final String value; + + public static MemberRole findByKey(String value) { + return Arrays.stream(MemberRole.values()) + .filter(role -> role.value.equals(value)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No role with key: " + value)); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/member/repository/MemberRepository.java b/src/main/java/com/moplus/moplus_server/domain/member/repository/MemberRepository.java new file mode 100644 index 0000000..bdfe25c --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/member/repository/MemberRepository.java @@ -0,0 +1,9 @@ +package com.moplus.moplus_server.domain.member.repository; + +import com.moplus.moplus_server.domain.member.domain.Member; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { + Optional findByEmail(String email); +} diff --git a/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java b/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java new file mode 100644 index 0000000..3fb39dc --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java @@ -0,0 +1,21 @@ +package com.moplus.moplus_server.domain.member.service; + +import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.domain.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + + @Transactional(readOnly = true) + public Member getMemberByEmail(String email) { + return memberRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("해당 이메일로 가입된 회원이 없습니다.")); + } + +} diff --git a/src/main/java/com/moplus/moplus_server/global/config/properties/PropertiesConfig.java b/src/main/java/com/moplus/moplus_server/global/config/properties/PropertiesConfig.java new file mode 100644 index 0000000..f616ece --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/config/properties/PropertiesConfig.java @@ -0,0 +1,13 @@ +package com.moplus.moplus_server.global.config.properties; + +import com.moplus.moplus_server.global.properties.jwt.JwtProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@EnableConfigurationProperties({ + JwtProperties.class +}) +@Configuration +public class PropertiesConfig { + +} diff --git a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java new file mode 100644 index 0000000..292a2e0 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java @@ -0,0 +1,112 @@ +package com.moplus.moplus_server.global.config.security; + +import com.moplus.moplus_server.domain.member.service.MemberService; +import com.moplus.moplus_server.global.security.filter.EmailPasswordAuthenticationFilter; +import com.moplus.moplus_server.global.security.handler.EmailPasswordSuccessHandler; +import com.moplus.moplus_server.global.security.provider.EmailPasswordAuthenticationProvider; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final MemberService memberService; + private final EmailPasswordSuccessHandler emailPasswordSuccessHandler; + private final AuthenticationConfiguration authenticationConfiguration; + + private String[] allowUrls = {"/", "/favicon.ico", "/swagger-ui/**", "/v3/**"}; + + @Value("${cors-allowed-origins}") + private List corsAllowedOrigins; + + @Bean + public WebSecurityCustomizer configure() { + // filter 안타게 무시 + return (web) -> web.ignoring().requestMatchers(allowUrls); + } + + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .cors(customizer -> customizer.configurationSource(corsConfigurationSource())) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(request -> request + .requestMatchers(allowUrls).permitAll() + .anyRequest().authenticated()); + + http + .exceptionHandling(exception -> + exception.authenticationEntryPoint((request, response, authException) -> + response.setStatus(HttpStatus.UNAUTHORIZED.value()))); // 인증,인가가 되지 않은 요청 시 발생시 + + http + .addFilterAt(emailPasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); +// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) +// .addFilterBefore(new JwtExceptionFilter(), JwtAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter() throws Exception { + EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter = new EmailPasswordAuthenticationFilter( + authenticationManager(authenticationConfiguration)); + emailPasswordAuthenticationFilter.setFilterProcessesUrl("/api/v1/auth/admin/login"); + emailPasswordAuthenticationFilter.setAuthenticationSuccessHandler(emailPasswordSuccessHandler); + emailPasswordAuthenticationFilter.afterPropertiesSet(); + return emailPasswordAuthenticationFilter; + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { + ProviderManager providerManager = (ProviderManager) authenticationConfiguration.getAuthenticationManager(); + providerManager.getProviders().add(emailPasswordAuthenticationProvider()); + return configuration.getAuthenticationManager(); + } + + @Bean + public EmailPasswordAuthenticationProvider emailPasswordAuthenticationProvider() { + return new EmailPasswordAuthenticationProvider(memberService, bCryptPasswordEncoder()); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(corsAllowedOrigins); + configuration.addAllowedMethod("*"); + configuration.setAllowedHeaders(List.of("*")); // 허용할 헤더 + configuration.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); // 모든 경로에 적용 + return source; + } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index fb9d7e3..2b572be 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -23,6 +23,9 @@ public enum ErrorCode { //이미지 IMAGE_FILE_EXTENSION_NOT_FOUND(HttpStatus.NOT_FOUND, "지원하지 않는 이미지 확장자입니다"), IMAGE_FILE_NOT_FOUND_IN_S3(HttpStatus.NOT_FOUND, "S3에 해당 이미지 파일을 찾을 수 없습니다"), + + //회원 + MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 회원을 찾을 수 없습니다"), ; diff --git a/src/main/java/com/moplus/moplus_server/global/properties/jwt/JwtProperties.java b/src/main/java/com/moplus/moplus_server/global/properties/jwt/JwtProperties.java new file mode 100644 index 0000000..bd71a2e --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/properties/jwt/JwtProperties.java @@ -0,0 +1,21 @@ +package com.moplus.moplus_server.global.properties.jwt; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "jwt") +public record JwtProperties( + String accessTokenSecret, + String refreshTokenSecret, + Long accessTokenExpirationTime, + Long refreshTokenExpirationTime, + String issuer +) { + + public Long accessTokenExpirationMilliTime() { + return accessTokenExpirationTime * 1000; + } + + public Long refreshTokenExpirationMilliTime() { + return refreshTokenExpirationTime * 1000; + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/security/AuthConstants.java b/src/main/java/com/moplus/moplus_server/global/security/AuthConstants.java new file mode 100644 index 0000000..312195b --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/AuthConstants.java @@ -0,0 +1,13 @@ +package com.moplus.moplus_server.global.security; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class AuthConstants { + + public static final String AUTH_HEADER = "Authorization"; + public static final String TOKEN_TYPE = "BEARER"; + public static final String REFRESH_TOKEN_HEADER = "RefreshToken"; + +} diff --git a/src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java b/src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java new file mode 100644 index 0000000..fdaf629 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java @@ -0,0 +1,55 @@ +package com.moplus.moplus_server.global.security; + +import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.global.properties.jwt.JwtProperties; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.security.Key; +import java.util.Date; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtUtil { + + public static final String TOKEN_ROLE_NAME = "role"; + private final JwtProperties jwtProperties; + + public String generateAccessToken(Member member) { + Date issuedAt = new Date(); + Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime()); + return Jwts.builder() + .setIssuer(jwtProperties.issuer()) + .setSubject(member.getId().toString()) + .claim(TOKEN_ROLE_NAME, member.getRole().getValue()) + .setIssuedAt(issuedAt) + .setExpiration(expiredAt) + .signWith(getAccessTokenKey()) + .compact(); + } + + public String generateRefreshToken(Member member) { + Date issuedAt = new Date(); + Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.refreshTokenExpirationMilliTime()); + + return Jwts.builder() + .setIssuer(jwtProperties.issuer()) + .setSubject(member.getId().toString()) + .claim(TOKEN_ROLE_NAME, member.getRole().getValue()) + .setIssuedAt(issuedAt) + .setExpiration(expiredAt) + .signWith(getRefreshTokenKey()) + .compact(); + } + + private Key getAccessTokenKey() { + return Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes()); + } + + private Key getRefreshTokenKey() { + return Keys.hmacShaKeyFor(jwtProperties.refreshTokenSecret().getBytes()); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/security/filter/EmailPasswordAuthenticationFilter.java b/src/main/java/com/moplus/moplus_server/global/security/filter/EmailPasswordAuthenticationFilter.java new file mode 100644 index 0000000..9c23e26 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/filter/EmailPasswordAuthenticationFilter.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.global.security.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moplus.moplus_server.domain.auth.dto.request.AdminLoginRequest; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +public class EmailPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + public EmailPasswordAuthenticationFilter(final AuthenticationManager authenticationManager) { + super.setAuthenticationManager(authenticationManager); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException { + final UsernamePasswordAuthenticationToken authRequest; + try { + final AdminLoginRequest loginRequest = new ObjectMapper().readValue(request.getInputStream(), + AdminLoginRequest.class); + authRequest = UsernamePasswordAuthenticationToken.unauthenticated(loginRequest.email(), + loginRequest.password()); + } catch (IOException exception) { + throw new NotFoundException(ErrorCode.MEMBER_NOT_FOUND); + } + setDetails(request, authRequest); + return this.getAuthenticationManager().authenticate(authRequest); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java new file mode 100644 index 0000000..a7cdaba --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java @@ -0,0 +1,29 @@ +package com.moplus.moplus_server.global.security.handler; + +import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.global.security.AuthConstants; +import com.moplus.moplus_server.global.security.JwtUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class EmailPasswordSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { + + private final JwtUtil jwtUtil; + + @Override + public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, + final Authentication authentication) { + Member member = (Member) authentication.getPrincipal(); + String accessToken = jwtUtil.generateAccessToken(member); + String refreshToken = jwtUtil.generateRefreshToken(member); + response.addHeader(AuthConstants.AUTH_HEADER, AuthConstants.TOKEN_TYPE + " " + accessToken); + response.addHeader(AuthConstants.REFRESH_TOKEN_HEADER, AuthConstants.TOKEN_TYPE + " " + refreshToken); + } + +} diff --git a/src/main/java/com/moplus/moplus_server/global/security/provider/EmailPasswordAuthenticationProvider.java b/src/main/java/com/moplus/moplus_server/global/security/provider/EmailPasswordAuthenticationProvider.java new file mode 100644 index 0000000..0012386 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/provider/EmailPasswordAuthenticationProvider.java @@ -0,0 +1,43 @@ +package com.moplus.moplus_server.global.security.provider; + +import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.domain.member.service.MemberService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@RequiredArgsConstructor +public class EmailPasswordAuthenticationProvider implements AuthenticationProvider { + + private final MemberService memberService; + private final BCryptPasswordEncoder passwordEncoder; + + @Override + public Authentication authenticate(final Authentication authentication) throws AuthenticationException { + final UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication; + final String memberEmail = token.getName(); + final String memberPassword = (String) token.getCredentials(); + + Member member = memberService.getMemberByEmail(memberEmail); + if (!memberPassword.equals(member.getPassword())) { + throw new BadCredentialsException(member.getEmail() + "Invalid password"); + } + + return UsernamePasswordAuthenticationToken.authenticated( + member, + memberPassword, + List.of(new SimpleGrantedAuthority(member.getRole().getValue()) + )); + } + + @Override + public boolean supports(Class authentication) { + return authentication.equals(UsernamePasswordAuthenticationToken.class); + } +} diff --git a/src/main/java/com/moplus/moplus_server/transactional/TransactionalPracticeService.java b/src/main/java/com/moplus/moplus_server/transactional/TransactionalPracticeService.java deleted file mode 100644 index 801e578..0000000 --- a/src/main/java/com/moplus/moplus_server/transactional/TransactionalPracticeService.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.moplus.moplus_server.transactional; - -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.service.client.PracticeTestService; - -public class TransactionalPracticeService { - - private final PracticeTestService practiceTestService; - - public TransactionalPracticeService(PracticeTestService practiceTestService) { - this.practiceTestService = practiceTestService; - } - - public void updateViewCount(Long practiceTestId) { - startTransaction(); - - practiceTestService.updateViewCount(practiceTestId); - - endTransaction(); - } - - private void startTransaction() { - // start transaction - } - - private void endTransaction() { - // end transaction - } -} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 5667c0f..72180bd 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -10,7 +10,3 @@ spring: use_sql_comments: true hibernate: ddl-auto: update - -logging: - level: - root: ERROR diff --git a/src/main/resources/application-security.yml b/src/main/resources/application-security.yml new file mode 100644 index 0000000..266da82 --- /dev/null +++ b/src/main/resources/application-security.yml @@ -0,0 +1,6 @@ +jwt: + access-token-secret: ${JWT_ACCESS_TOKEN_SECRET:} + refresh-token-secret: ${JWT_REFRESH_TOKEN_SECRET:} + access-token-expiration-time: ${JWT_ACCESS_TOKEN_EXPIRATION_TIME:36000} #10시간 + refresh-token-expiration-time: ${JWT_REFRESH_TOKEN_EXPIRATION_TIME:604800} #7일 + issuer: ${JWT_ISSUER:} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7be66c3..c5ca596 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,10 +2,15 @@ spring: profiles: active: ${profile} group: - local: "local" + local: "local, datasource" dev: "dev" prod: "prod" include: - aws + - security mvc: ignore-default-favicon: true + +cors-allowed-origins: + http://localhost:8080, + http://localhost:3000, \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java b/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java new file mode 100644 index 0000000..9354676 --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java @@ -0,0 +1,70 @@ +package com.moplus.moplus_server.domain.auth.controller; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moplus.moplus_server.domain.auth.dto.request.AdminLoginRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("h2test") +@Sql({"/auth-test-data.sql"}) +class AuthControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private WebApplicationContext context; + + @BeforeEach + public void setMockMvc() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(springSecurity()).build(); + } + + @Test + void 어드민_로그인_성공() throws Exception { + + AdminLoginRequest request = new AdminLoginRequest("admin@example.com", "password123"); // DTO 객체 생성 + String requestBody = objectMapper.writeValueAsString(request); + + mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/auth/admin/login") + .contentType("application/json") + .content(requestBody)) + .andExpect(status().isOk()) // 200 응답 확인 + .andExpect(header().exists("Authorization")) + .andExpect(header().exists("RefreshToken")); + + } + + @Test + void 어드민_로그인_실패() throws Exception { + + AdminLoginRequest request = new AdminLoginRequest("admin@example.com", "wrong123"); // DTO 객체 생성 + String requestBody = objectMapper.writeValueAsString(request); + + mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/auth/admin/login") + .contentType("application/json") + .content(requestBody)) + .andExpect(status().isUnauthorized()); // 401 응답 확인 + } +} \ No newline at end of file diff --git a/src/test/resources/application-h2test.yml b/src/test/resources/application-h2test.yml new file mode 100644 index 0000000..f1bf6e6 --- /dev/null +++ b/src/test/resources/application-h2test.yml @@ -0,0 +1,33 @@ +spring: + jpa: + hibernate: + ddl-auto: create-drop + show-sql: true + + datasource: + url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false;MODE=MYSQL + +jwt: + access-token-secret: 0eFd7h2PLQ5tH7v3jBcXFr6L8hYh5u3g1kFxWrZ0dJc= + refresh-token-secret: q8aV4Mf4r7l5u9OxC7ZtVx2qY2eDz9Tw5uDl9JQ6SJI= + access-token-expiration-time: 3600 #1시간 + refresh-token-expiration-time: 604800 #7일 + issuer: test + +cloud: + aws: + s3: + bucket: test + signature-version: AWS4-HMAC-SHA256 + credentials: + access-key: 0eFd7h2PLQ5tH7v3jBcXFr6L8hYh5u3g1kFxWrZ0dJc= + secret-key: q8aV4Mf4r7l5u9OxC7ZtVx2qY2eDz9Tw5uDl9JQ6SJI= + region: + static: test + auto: false + stack: + auto: false + +cors-allowed-origins: + http://localhost:8080, + http://localhost:3000, diff --git a/src/test/resources/auth-test-data.sql b/src/test/resources/auth-test-data.sql new file mode 100644 index 0000000..f757a22 --- /dev/null +++ b/src/test/resources/auth-test-data.sql @@ -0,0 +1,4 @@ +INSERT INTO member (deleted, created_at, update_at, member_id, email, password, role) +VALUES (false, '2024-07-24 21:27:20.000000', '2024-07-24 21:27:21.000000', 1, 'admin@example.com', + 'password123', 'ADMIN'); + From c8b6a3532839d41194fcc9a3c044903b05670052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:45:48 +0900 Subject: [PATCH 013/115] =?UTF-8?q?[fix]=20=EB=8F=99=EC=8B=9C=EC=84=B1=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20DB=20h2=20=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OptimisticLockPracticeTestFacadeTest.java | 54 ------------------- .../client/PracticeTestServiceTest.java | 14 +---- .../client/ProblemServiceConcurrencyTest.java | 9 ++-- 3 files changed, 5 insertions(+), 72 deletions(-) delete mode 100644 src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/OptimisticLockPracticeTestFacadeTest.java diff --git a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/OptimisticLockPracticeTestFacadeTest.java b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/OptimisticLockPracticeTestFacadeTest.java deleted file mode 100644 index 487cd63..0000000 --- a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/OptimisticLockPracticeTestFacadeTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.moplus.moplus_server.domain.practiceTest.service.client; - -import static org.junit.jupiter.api.Assertions.*; - -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@ActiveProfiles("test") -class OptimisticLockPracticeTestFacadeTest { - @Autowired - private OptimisticLockPracticeTestFacade optimisticLockPracticeTestFacade; - - @Autowired - private PracticeTestRepository practiceTestRepository; - - @BeforeEach - void setup() { - PracticeTest practiceTest = new PracticeTest(); - practiceTestRepository.save(practiceTest); - } - - @Test - public void 동시에_조회수가_정상적으로_업데이트_되어야한다() throws InterruptedException { - Long practiceTestId = 1L; - int threadCount = 100; - ExecutorService executorService = Executors.newFixedThreadPool(36); - CountDownLatch countDownLatch = new CountDownLatch(threadCount); - - for (int i = 0; i < threadCount; i++) { - executorService.submit(() -> { - try { - optimisticLockPracticeTestFacade.updateViewCount(practiceTestId); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - countDownLatch.countDown(); - } - }); - } - countDownLatch.await(); - - PracticeTest practiceTest = practiceTestRepository.findById(practiceTestId).orElseThrow(); - assertEquals(threadCount, practiceTest.getViewCount()); - } -} \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java index 092c189..6558a15 100644 --- a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java @@ -1,31 +1,21 @@ package com.moplus.moplus_server.domain.practiceTest.service.client; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; import com.moplus.moplus_server.domain.practiceTest.repository.ProblemRepository; -import jakarta.transaction.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.jdbc.Sql; @SpringBootTest -@ActiveProfiles("test") +@ActiveProfiles("h2test") class PracticeTestServiceTest { @Autowired diff --git a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemServiceConcurrencyTest.java b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemServiceConcurrencyTest.java index eb9936b..b8483f7 100644 --- a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemServiceConcurrencyTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemServiceConcurrencyTest.java @@ -2,10 +2,8 @@ import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; import com.moplus.moplus_server.domain.practiceTest.domain.Problem; -import com.moplus.moplus_server.domain.practiceTest.dto.client.response.ProblemGetResponse; import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; import com.moplus.moplus_server.domain.practiceTest.repository.ProblemRepository; -import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -17,7 +15,7 @@ import org.springframework.test.context.ActiveProfiles; @SpringBootTest -@ActiveProfiles("test") +@ActiveProfiles("h2test") public class ProblemServiceConcurrencyTest { @Autowired @@ -58,9 +56,8 @@ public void testConcurrentUpdateCorrectRateAndCount() throws InterruptedExceptio CountDownLatch countDownLatch = new CountDownLatch(threadCount); Problem problem = problemRepository.findById(1L).orElseThrow(); - for (int i = 0; i < threadCount; i++) { - if(i % 2 == 0){ + if (i % 2 == 0) { executorService.submit(() -> { try { practiceTestService.updateViewCount(practiceTestId); @@ -82,7 +79,7 @@ public void testConcurrentUpdateCorrectRateAndCount() throws InterruptedExceptio PracticeTest practiceTest = practiceTestRepository.findById(practiceTestId).orElseThrow(); - Assertions.assertEquals(threadCount/2, practiceTest.getViewCount()); + Assertions.assertEquals(threadCount / 2, practiceTest.getViewCount()); } } From 24d496d22638f714bbae3ecb6136d0d91bcf665e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:47:45 +0900 Subject: [PATCH 014/115] =?UTF-8?q?[fix]=20=EB=8F=99=EC=8B=9C=EC=84=B1=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/ProblemServiceConcurrencyTest.java | 85 ------------------- 1 file changed, 85 deletions(-) delete mode 100644 src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemServiceConcurrencyTest.java diff --git a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemServiceConcurrencyTest.java b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemServiceConcurrencyTest.java deleted file mode 100644 index b8483f7..0000000 --- a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemServiceConcurrencyTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.moplus.moplus_server.domain.practiceTest.service.client; - -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; -import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.practiceTest.repository.ProblemRepository; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@ActiveProfiles("h2test") -public class ProblemServiceConcurrencyTest { - - @Autowired - private PracticeTestService practiceTestService; - - @Autowired - private PracticeTestRepository practiceTestRepository; - - @Autowired - private ProblemRepository problemRepository; - - @Autowired - private ProblemService problemService; - - @BeforeEach - void setup() { - PracticeTest practiceTest = new PracticeTest(); - practiceTestRepository.save(practiceTest); - PracticeTest entity = practiceTestRepository.findById(practiceTest.getId()).orElseThrow(); - - Problem problem = Problem.builder() - .problemNumber("1") - .answer("42") - .point(5) - .incorrectNum(10L) - .practiceTest(entity) // Assume we have a PracticeTest entity linked here - .correctRate(0.5) - .build(); - problemRepository.save(problem); - } - - @Test - public void testConcurrentUpdateCorrectRateAndCount() throws InterruptedException { - Long practiceTestId = 1L; - Long problemId = 1L; - int threadCount = 100; - ExecutorService executorService = Executors.newFixedThreadPool(36); - CountDownLatch countDownLatch = new CountDownLatch(threadCount); - Problem problem = problemRepository.findById(1L).orElseThrow(); - - for (int i = 0; i < threadCount; i++) { - if (i % 2 == 0) { - executorService.submit(() -> { - try { - practiceTestService.updateViewCount(practiceTestId); - } finally { - countDownLatch.countDown(); - } - }); - } else { - executorService.submit(() -> { - try { - problemService.updateCorrectRate(practiceTestId, "1", 0.7); - } finally { - countDownLatch.countDown(); - } - }); - } - } - countDownLatch.await(); - - PracticeTest practiceTest = practiceTestRepository.findById(practiceTestId).orElseThrow(); - - Assertions.assertEquals(threadCount / 2, practiceTest.getViewCount()); - } - -} From 44e718d0c8811a8ce5860ab244812c9420ecb10c Mon Sep 17 00:00:00 2001 From: sejoon Date: Wed, 15 Jan 2025 16:55:44 +0900 Subject: [PATCH 015/115] =?UTF-8?q?[fix/#9]=20=EB=8B=A8=EC=88=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=A1=B0=ED=9A=8C=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20repository?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/repository/MemberRepository.java | 6 ++++++ .../moplus_server/domain/member/service/MemberService.java | 4 +--- .../moplus_server/global/error/exception/ErrorCode.java | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/member/repository/MemberRepository.java b/src/main/java/com/moplus/moplus_server/domain/member/repository/MemberRepository.java index bdfe25c..9eb0168 100644 --- a/src/main/java/com/moplus/moplus_server/domain/member/repository/MemberRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/member/repository/MemberRepository.java @@ -1,9 +1,15 @@ package com.moplus.moplus_server.domain.member.repository; import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { Optional findByEmail(String email); + + default Member findByEmailOrThrow(String email) { + return findByEmail(email).orElseThrow(() -> new NotFoundException(ErrorCode.MEMBER_NOT_FOUND)); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java b/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java index 3fb39dc..a16f50b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java +++ b/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java @@ -14,8 +14,6 @@ public class MemberService { @Transactional(readOnly = true) public Member getMemberByEmail(String email) { - return memberRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("해당 이메일로 가입된 회원이 없습니다.")); + return memberRepository.findByEmailOrThrow(email); } - } diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 2b572be..bc30576 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -5,7 +5,11 @@ @Getter public enum ErrorCode { + + //공통 INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류, 관리자에게 문의하세요"), + INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "잘못된 입력 값입니다"), + BAD_CREDENTIALS(HttpStatus.UNAUTHORIZED, "잘못된 인증 정보입니다"), //모의고사 PRACTICE_TEST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 모의고사를 찾을 수 없습니다"), From 88c2033afd3370fb0745da96bed8b30dd2d894c5 Mon Sep 17 00:00:00 2001 From: sejoon Date: Wed, 15 Jan 2025 16:57:08 +0900 Subject: [PATCH 016/115] =?UTF-8?q?[fix/#9]=20parsing=20=EC=98=88=EC=99=B8?= =?UTF-8?q?,=20NotFound=20=EC=98=88=EC=99=B8=20BadCredentialsException?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC=20=EB=B0=8F=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + .../domain/member/domain/Member.java | 4 + .../config/security/SecurityConfig.java | 2 +- .../EmailPasswordAuthenticationFilter.java | 4 +- .../EmailPasswordAuthenticationProvider.java | 25 ++++-- .../auth/controller/AuthControllerTest.java | 84 ++++++++++++++----- 6 files changed, 95 insertions(+), 27 deletions(-) diff --git a/build.gradle b/build.gradle index 96a2e6a..47c4de6 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,9 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + // validator + implementation 'commons-validator:commons-validator:1.7' + } tasks.named('test') { diff --git a/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java b/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java index 13579cd..289a78d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java +++ b/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java @@ -35,4 +35,8 @@ public Member(String nickname, String email, String password, MemberRole role) { this.password = password; this.role = role; } + + public boolean isMatchingPassword(String password) { + return this.password.equals(password); + } } diff --git a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java index 292a2e0..64d3142 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java @@ -89,7 +89,7 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration c @Bean public EmailPasswordAuthenticationProvider emailPasswordAuthenticationProvider() { - return new EmailPasswordAuthenticationProvider(memberService, bCryptPasswordEncoder()); + return new EmailPasswordAuthenticationProvider(memberService); } @Bean diff --git a/src/main/java/com/moplus/moplus_server/global/security/filter/EmailPasswordAuthenticationFilter.java b/src/main/java/com/moplus/moplus_server/global/security/filter/EmailPasswordAuthenticationFilter.java index 9c23e26..310ad74 100644 --- a/src/main/java/com/moplus/moplus_server/global/security/filter/EmailPasswordAuthenticationFilter.java +++ b/src/main/java/com/moplus/moplus_server/global/security/filter/EmailPasswordAuthenticationFilter.java @@ -3,11 +3,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.moplus.moplus_server.domain.auth.dto.request.AdminLoginRequest; import com.moplus.moplus_server.global.error.exception.ErrorCode; -import com.moplus.moplus_server.global.error.exception.NotFoundException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -29,7 +29,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ authRequest = UsernamePasswordAuthenticationToken.unauthenticated(loginRequest.email(), loginRequest.password()); } catch (IOException exception) { - throw new NotFoundException(ErrorCode.MEMBER_NOT_FOUND); + throw new BadCredentialsException(ErrorCode.INVALID_INPUT_VALUE.getMessage()); } setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); diff --git a/src/main/java/com/moplus/moplus_server/global/security/provider/EmailPasswordAuthenticationProvider.java b/src/main/java/com/moplus/moplus_server/global/security/provider/EmailPasswordAuthenticationProvider.java index 0012386..a284656 100644 --- a/src/main/java/com/moplus/moplus_server/global/security/provider/EmailPasswordAuthenticationProvider.java +++ b/src/main/java/com/moplus/moplus_server/global/security/provider/EmailPasswordAuthenticationProvider.java @@ -2,21 +2,27 @@ import com.moplus.moplus_server.domain.member.domain.Member; import com.moplus.moplus_server.domain.member.service.MemberService; +import com.moplus.moplus_server.global.error.exception.ErrorCode; import java.util.List; import lombok.RequiredArgsConstructor; +import org.apache.commons.validator.routines.EmailValidator; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @RequiredArgsConstructor public class EmailPasswordAuthenticationProvider implements AuthenticationProvider { private final MemberService memberService; - private final BCryptPasswordEncoder passwordEncoder; + + private static void validateEmail(String memberEmail) { + if (!EmailValidator.getInstance().isValid(memberEmail)) { + throw new BadCredentialsException(ErrorCode.BAD_CREDENTIALS.getMessage()); + } + } @Override public Authentication authenticate(final Authentication authentication) throws AuthenticationException { @@ -24,9 +30,10 @@ public Authentication authenticate(final Authentication authentication) throws A final String memberEmail = token.getName(); final String memberPassword = (String) token.getCredentials(); - Member member = memberService.getMemberByEmail(memberEmail); - if (!memberPassword.equals(member.getPassword())) { - throw new BadCredentialsException(member.getEmail() + "Invalid password"); + validateEmail(memberEmail); + final Member member = getMemberByEmail(memberEmail); + if (!member.isMatchingPassword(memberPassword)) { + throw new BadCredentialsException(ErrorCode.BAD_CREDENTIALS.getMessage()); } return UsernamePasswordAuthenticationToken.authenticated( @@ -36,6 +43,14 @@ public Authentication authenticate(final Authentication authentication) throws A )); } + private Member getMemberByEmail(String memberEmail) { + try { + return memberService.getMemberByEmail(memberEmail); + } catch (Exception e) { + throw new BadCredentialsException(ErrorCode.BAD_CREDENTIALS.getMessage()); + } + } + @Override public boolean supports(Class authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); diff --git a/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java b/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java index 9354676..95ee4e7 100644 --- a/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java @@ -7,7 +7,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.moplus.moplus_server.domain.auth.dto.request.AdminLoginRequest; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -41,30 +44,73 @@ public void setMockMvc() { .apply(springSecurity()).build(); } - @Test - void 어드민_로그인_성공() throws Exception { + @Nested + class 어드민_로그인 { - AdminLoginRequest request = new AdminLoginRequest("admin@example.com", "password123"); // DTO 객체 생성 - String requestBody = objectMapper.writeValueAsString(request); + @Test + void 성공() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/auth/admin/login") - .contentType("application/json") - .content(requestBody)) - .andExpect(status().isOk()) // 200 응답 확인 - .andExpect(header().exists("Authorization")) - .andExpect(header().exists("RefreshToken")); + AdminLoginRequest request = new AdminLoginRequest("admin@example.com", "password123"); + String requestBody = objectMapper.writeValueAsString(request); - } + mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/auth/admin/login") + .contentType("application/json") + .content(requestBody)) + .andExpect(status().isOk()) // 200 응답 확인 + .andExpect(header().exists("Authorization")) + .andExpect(header().exists("RefreshToken")); + + } + + @Test + void 잘못된_요청_본문() throws Exception { + + record TempRecord(String data) { + } + + TempRecord request = new TempRecord("임시 테스트 요청 본문"); + String requestBody = objectMapper.writeValueAsString(request); + + mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/auth/admin/login") + .contentType("application/json") + .content(requestBody)) + .andExpect(status().isUnauthorized()); + } + + @ParameterizedTest + @ValueSource(strings = { + "plainaddress", // 이메일 형식이 아님 + "@missingusername.com", // 사용자명 없음 + "username@.com", // 도메인 이름 없음 + "username@com", // 잘못된 도메인 + "username@domain..com", // 연속된 점 + "username@domain,com", // 쉼표 포함 + "username@domain space.com", // 공백 포함 + "username@domain.com space", // 공백 포함 + "username@domain#com", // 특수문자 포함 + "" // 빈 문자열 + }) + void 잘못된_이메일_양식(String email) throws Exception { + + AdminLoginRequest request = new AdminLoginRequest(email, "password123"); + String requestBody = objectMapper.writeValueAsString(request); + + mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/auth/admin/login") + .contentType("application/json") + .content(requestBody)) + .andExpect(status().isUnauthorized()); // 401 응답 확인 + } - @Test - void 어드민_로그인_실패() throws Exception { + @Test + void 실패() throws Exception { - AdminLoginRequest request = new AdminLoginRequest("admin@example.com", "wrong123"); // DTO 객체 생성 - String requestBody = objectMapper.writeValueAsString(request); + AdminLoginRequest request = new AdminLoginRequest("admin@example.com", "wrong123"); + String requestBody = objectMapper.writeValueAsString(request); - mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/auth/admin/login") - .contentType("application/json") - .content(requestBody)) - .andExpect(status().isUnauthorized()); // 401 응답 확인 + mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/auth/admin/login") + .contentType("application/json") + .content(requestBody)) + .andExpect(status().isUnauthorized()); // 401 응답 확인 + } } } \ No newline at end of file From d67a3af8b7a2f259c2cd7e73a92bbf9d0b4ed474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:21:53 +0900 Subject: [PATCH 017/115] =?UTF-8?q?[feat/#11]=20jwt=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=20=ED=95=84=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 1 + .../domain/member/service/MemberService.java | 5 + .../config/security/SecurityConfig.java | 60 +++++++--- .../global/error/exception/ErrorCode.java | 11 ++ .../exception/JwtInvalidException.java | 14 +++ .../filter/JwtAuthenticationFilter.java | 45 +++++++ .../handler/EmailPasswordSuccessHandler.java | 2 +- .../security/provider/JwtTokenProvider.java | 69 +++++++++++ .../token/JwtAuthenticationToken.java | 38 ++++++ .../global/security/{ => utils}/JwtUtil.java | 15 ++- .../global/security/utils/JwtUtilTest.java | 113 ++++++++++++++++++ 11 files changed, 352 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/global/security/exception/JwtInvalidException.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/filter/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/provider/JwtTokenProvider.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/token/JwtAuthenticationToken.java rename src/main/java/com/moplus/moplus_server/global/security/{ => utils}/JwtUtil.java (76%) create mode 100644 src/test/java/com/moplus/moplus_server/global/security/utils/JwtUtilTest.java diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java index 9a3957e..48d6cfd 100644 --- a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java @@ -36,4 +36,5 @@ public void adminLogin( ) { // 실제 처리는 Security 필터에서 이루어지며, 이 메서드는 Swagger 명세용입니다. } + } diff --git a/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java b/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java index a16f50b..0940f76 100644 --- a/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java +++ b/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java @@ -16,4 +16,9 @@ public class MemberService { public Member getMemberByEmail(String email) { return memberRepository.findByEmailOrThrow(email); } + + @Transactional(readOnly = true) + public Member getMemberById(Long id) { + return memberRepository.findById(id).orElseThrow(); + } } diff --git a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java index 64d3142..2ee9340 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java @@ -2,8 +2,11 @@ import com.moplus.moplus_server.domain.member.service.MemberService; import com.moplus.moplus_server.global.security.filter.EmailPasswordAuthenticationFilter; +import com.moplus.moplus_server.global.security.filter.JwtAuthenticationFilter; import com.moplus.moplus_server.global.security.handler.EmailPasswordSuccessHandler; import com.moplus.moplus_server.global.security.provider.EmailPasswordAuthenticationProvider; +import com.moplus.moplus_server.global.security.provider.JwtTokenProvider; +import com.moplus.moplus_server.global.security.utils.JwtUtil; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -11,8 +14,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; @@ -32,7 +34,7 @@ public class SecurityConfig { private final MemberService memberService; private final EmailPasswordSuccessHandler emailPasswordSuccessHandler; - private final AuthenticationConfiguration authenticationConfiguration; + private final JwtUtil jwtUtil; private String[] allowUrls = {"/", "/favicon.ico", "/swagger-ui/**", "/v3/**"}; @@ -45,6 +47,17 @@ public WebSecurityCustomizer configure() { return (web) -> web.ignoring().requestMatchers(allowUrls); } + @Bean + public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { + AuthenticationManagerBuilder authenticationManagerBuilder = + http.getSharedObject(AuthenticationManagerBuilder.class); + authenticationManagerBuilder + .authenticationProvider(emailPasswordAuthenticationProvider()) + .authenticationProvider(jwtTokenProvider()); + authenticationManagerBuilder.parentAuthenticationManager(null); + return authenticationManagerBuilder.build(); + } + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -62,34 +75,43 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { exception.authenticationEntryPoint((request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value()))); // 인증,인가가 되지 않은 요청 시 발생시 + http.authenticationManager(authenticationManager(http)); + http - .addFilterAt(emailPasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); -// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) -// .addFilterBefore(new JwtExceptionFilter(), JwtAuthenticationFilter.class); + .addFilterAt(emailPasswordAuthenticationFilter(authenticationManager(http)), + UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(jwtAuthenticationFilter(authenticationManager(http)), + UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean - public EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter() throws Exception { - EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter = new EmailPasswordAuthenticationFilter( - authenticationManager(authenticationConfiguration)); - emailPasswordAuthenticationFilter.setFilterProcessesUrl("/api/v1/auth/admin/login"); - emailPasswordAuthenticationFilter.setAuthenticationSuccessHandler(emailPasswordSuccessHandler); - emailPasswordAuthenticationFilter.afterPropertiesSet(); - return emailPasswordAuthenticationFilter; + public EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter( + AuthenticationManager authenticationManager) throws Exception { + EmailPasswordAuthenticationFilter filter = new EmailPasswordAuthenticationFilter(authenticationManager); + filter.setFilterProcessesUrl("/api/v1/auth/admin/login"); + filter.setAuthenticationSuccessHandler(emailPasswordSuccessHandler); + filter.afterPropertiesSet(); + return filter; } @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { - ProviderManager providerManager = (ProviderManager) authenticationConfiguration.getAuthenticationManager(); - providerManager.getProviders().add(emailPasswordAuthenticationProvider()); - return configuration.getAuthenticationManager(); + public EmailPasswordAuthenticationProvider emailPasswordAuthenticationProvider() { + return new EmailPasswordAuthenticationProvider(memberService); } @Bean - public EmailPasswordAuthenticationProvider emailPasswordAuthenticationProvider() { - return new EmailPasswordAuthenticationProvider(memberService); + public JwtAuthenticationFilter jwtAuthenticationFilter(AuthenticationManager authenticationManager) + throws Exception { + JwtAuthenticationFilter filter = new JwtAuthenticationFilter(authenticationManager); + filter.afterPropertiesSet(); + return filter; + } + + @Bean + public JwtTokenProvider jwtTokenProvider() { + return new JwtTokenProvider(jwtUtil, memberService); } @Bean diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index bc30576..31735e8 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -11,6 +11,17 @@ public enum ErrorCode { INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "잘못된 입력 값입니다"), BAD_CREDENTIALS(HttpStatus.UNAUTHORIZED, "잘못된 인증 정보입니다"), + //Auth + AUTH_NOT_FOUND(HttpStatus.UNAUTHORIZED, "시큐리티 인증 정보를 찾을 수 없습니다."), + UNKNOWN_ERROR(HttpStatus.UNAUTHORIZED, "알 수 없는 에러"), + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 Token입니다"), + UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "토큰 길이 및 형식이 다른 Token입니다"), + WRONG_TYPE_TOKEN(HttpStatus.UNAUTHORIZED, "서명이 잘못된 토큰입니다."), + ACCESS_DENIED(HttpStatus.UNAUTHORIZED, "토큰이 없습니다"), + TOKEN_SUBJECT_FORMAT_ERROR(HttpStatus.UNAUTHORIZED, "Subject 값에 Long 타입이 아닌 다른 타입이 들어있습니다."), + AT_EXPIRED_AND_RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AT는 만료되었고 RT는 비어있습니다."), + RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "RT가 비어있습니다"), + //모의고사 PRACTICE_TEST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 모의고사를 찾을 수 없습니다"), diff --git a/src/main/java/com/moplus/moplus_server/global/security/exception/JwtInvalidException.java b/src/main/java/com/moplus/moplus_server/global/security/exception/JwtInvalidException.java new file mode 100644 index 0000000..e95b846 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/exception/JwtInvalidException.java @@ -0,0 +1,14 @@ +package com.moplus.moplus_server.global.security.exception; + +import org.springframework.security.core.AuthenticationException; + +public class JwtInvalidException extends AuthenticationException { + + public JwtInvalidException(String msg) { + super(msg); + } + + public JwtInvalidException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/moplus/moplus_server/global/security/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..1f37c3a --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/filter/JwtAuthenticationFilter.java @@ -0,0 +1,45 @@ +package com.moplus.moplus_server.global.security.filter; + +import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + public static final String TOKEN_PREFIX = "Bearer "; + + private final AuthenticationManager authenticationManager; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String accessToken = extractAccessTokenFromHeader(request); + + if (StringUtils.hasText(accessToken)) { + Authentication jwtAuthenticationToken = new JwtAuthenticationToken(accessToken); + Authentication authentication = authenticationManager.authenticate(jwtAuthenticationToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request, response); + } + + private String extractAccessTokenFromHeader(HttpServletRequest request) { + return Optional.ofNullable(request.getHeader("Authorization")) + .filter(header -> header.startsWith(TOKEN_PREFIX)) + .map(header -> header.replace(TOKEN_PREFIX, "")) + .orElse(null); + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java index a7cdaba..9fe78fc 100644 --- a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java +++ b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java @@ -2,7 +2,7 @@ import com.moplus.moplus_server.domain.member.domain.Member; import com.moplus.moplus_server.global.security.AuthConstants; -import com.moplus.moplus_server.global.security.JwtUtil; +import com.moplus.moplus_server.global.security.utils.JwtUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/moplus/moplus_server/global/security/provider/JwtTokenProvider.java b/src/main/java/com/moplus/moplus_server/global/security/provider/JwtTokenProvider.java new file mode 100644 index 0000000..7d8a608 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/provider/JwtTokenProvider.java @@ -0,0 +1,69 @@ +package com.moplus.moplus_server.global.security.provider; + +import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.domain.member.service.MemberService; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.security.exception.JwtInvalidException; +import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken; +import com.moplus.moplus_server.global.security.utils.JwtUtil; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.security.SignatureException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class JwtTokenProvider implements AuthenticationProvider { + + private final JwtUtil jwtUtil; + private final MemberService memberService; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Claims claims = getClaims(authentication); + final Member member = getMemberById(claims.getSubject()); + + return new JwtAuthenticationToken( + member, + "", + List.of(new SimpleGrantedAuthority(member.getRole().getValue()) + )); + } + + private Claims getClaims(Authentication authentication) { + Claims claims; + try { + claims = jwtUtil.getAccessTokenClaims(authentication); + } catch (ExpiredJwtException expiredJwtException) { + throw new JwtInvalidException(ErrorCode.EXPIRED_TOKEN.getMessage()); + } catch (SignatureException signatureException) { + throw new JwtInvalidException(ErrorCode.WRONG_TYPE_TOKEN.getMessage()); + } catch (MalformedJwtException malformedJwtException) { + throw new JwtInvalidException(ErrorCode.UNSUPPORTED_TOKEN.getMessage()); + } catch (IllegalArgumentException illegalArgumentException) { + throw new JwtInvalidException(ErrorCode.UNKNOWN_ERROR.getMessage()); + } + return claims; + } + + private Member getMemberById(String id) { + try { + return memberService.getMemberById(Long.parseLong(id)); + } catch (Exception e) { + throw new BadCredentialsException(ErrorCode.BAD_CREDENTIALS.getMessage()); + } + } + + @Override + public boolean supports(Class authentication) { + return JwtAuthenticationToken.class.isAssignableFrom(authentication); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/security/token/JwtAuthenticationToken.java b/src/main/java/com/moplus/moplus_server/global/security/token/JwtAuthenticationToken.java new file mode 100644 index 0000000..c9f6d5f --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/token/JwtAuthenticationToken.java @@ -0,0 +1,38 @@ +package com.moplus.moplus_server.global.security.token; + +import java.util.Collection; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +public class JwtAuthenticationToken extends AbstractAuthenticationToken { + + private String jsonWebToken; + private Object principal; + private Object credentials; + + public JwtAuthenticationToken(String jsonWebToken) { + super(null); + this.jsonWebToken = jsonWebToken; + this.setAuthenticated(false); + } + + public JwtAuthenticationToken(Object principal, Object credentials, + Collection authorities) { + super(authorities); + this.principal = principal; + this.credentials = credentials; + super.setAuthenticated(true); + } + + public Object getCredentials() { + return credentials; + } + + public Object getPrincipal() { + return this.principal; + } + + public String getJsonWebToken() { + return this.jsonWebToken; + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java b/src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java similarity index 76% rename from src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java rename to src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java index fdaf629..d282029 100644 --- a/src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java +++ b/src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java @@ -1,13 +1,16 @@ -package com.moplus.moplus_server.global.security; +package com.moplus.moplus_server.global.security.utils; import com.moplus.moplus_server.domain.member.domain.Member; import com.moplus.moplus_server.global.properties.jwt.JwtProperties; +import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken; +import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import java.security.Key; import java.util.Date; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @Slf4j @@ -45,6 +48,16 @@ public String generateRefreshToken(Member member) { .compact(); } + public Claims getAccessTokenClaims(Authentication authentication) { + + return Jwts.parserBuilder() + .requireIssuer(jwtProperties.issuer()) + .setSigningKey(getAccessTokenKey()) + .build() + .parseClaimsJws(((JwtAuthenticationToken) authentication).getJsonWebToken()) + .getBody(); + } + private Key getAccessTokenKey() { return Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes()); } diff --git a/src/test/java/com/moplus/moplus_server/global/security/utils/JwtUtilTest.java b/src/test/java/com/moplus/moplus_server/global/security/utils/JwtUtilTest.java new file mode 100644 index 0000000..e96648c --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/global/security/utils/JwtUtilTest.java @@ -0,0 +1,113 @@ +package com.moplus.moplus_server.global.security.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +import com.moplus.moplus_server.global.properties.jwt.JwtProperties; +import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; +import java.security.Key; +import java.util.Date; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; + +@ExtendWith(MockitoExtension.class) +public class JwtUtilTest { + + @Mock + private JwtProperties jwtProperties; + + @InjectMocks + private JwtUtil jwtUtil; + + private String validToken; + private Key key; + + @BeforeEach + public void setup() { + // Mock JwtProperties + when(jwtProperties.issuer()).thenReturn("testIssuer"); + when(jwtProperties.accessTokenSecret()).thenReturn( + "mySecretKeymySecretKeymySecretKeymySecretKey"); // 256-bit key + when(jwtProperties.accessTokenExpirationMilliTime()).thenReturn(7200000L); // 1 hour + + // Generate a test token + key = Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes()); + Date issuedAt = new Date(); // 3 hour ago + Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime()); + validToken = Jwts.builder() + .setIssuer(jwtProperties.issuer()) + .setSubject("1") + .claim("role", "ROLE_USER") + .setIssuedAt(issuedAt) + .setExpiration(expiredAt) + .signWith(key) + .compact(); + } + + @Test + public void 유효한_토큰_통과() { + + Authentication jwtAuthenticationToken = new JwtAuthenticationToken(validToken); + + // Act + Claims claimsJws = jwtUtil.getAccessTokenClaims(jwtAuthenticationToken); + + // Assert + assertNotNull(claimsJws); + assertEquals("testIssuer", claimsJws.getIssuer()); + assertEquals("1", claimsJws.getSubject()); + assertEquals("ROLE_USER", claimsJws.get("role", String.class)); + } + + @Test + public void 만료된_토큰_예외() { + Date issuedAt = new Date(System.currentTimeMillis() - 10800000L); // 3 hour ago + Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime()); + String expiredToken = Jwts.builder() + .setIssuer(jwtProperties.issuer()) + .setSubject("12345") + .claim("role", "ROLE_USER") + .setIssuedAt(issuedAt) + .setExpiration(expiredAt) // 1 hour ago + .signWith(key) + .compact(); + + Authentication jwtAuthenticationToken = new JwtAuthenticationToken(expiredToken); + + assertThrows(ExpiredJwtException.class, + () -> jwtUtil.getAccessTokenClaims(jwtAuthenticationToken)); + } + + @Test + public void 조작된_토큰_예외() { + //토큰 변형 + String invalidSignatureToken = validToken.substring(0, validToken.length() - 1) + "@"; + Authentication jwtAuthenticationToken = new JwtAuthenticationToken(invalidSignatureToken); + assertThrows(SignatureException.class, () -> { + jwtUtil.getAccessTokenClaims(jwtAuthenticationToken); + }); + } + + @Test + public void jwt_토큰_형식이_아닌_토큰_예외() { + //형식이 jwt 토큰 형식 조차 아닌 토큰 + String malformedToken = "malformed.token.here"; + Authentication jwtAuthenticationToken = new JwtAuthenticationToken(malformedToken); + assertThrows(MalformedJwtException.class, () -> { + jwtUtil.getAccessTokenClaims(jwtAuthenticationToken); + }); + } +} From 7ab0163cf3277c646e99d5efbf82a03fe9c10ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:49:22 +0900 Subject: [PATCH 018/115] =?UTF-8?q?[docs/#15]=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20api=20swagger?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailResultApplicationController.java | 5 ++++- .../TestResult/controller/RatingController.java | 2 ++ .../TestResult/controller/TestResultController.java | 12 +++++++----- .../api/admin/PracticeTestAdminController.java | 8 ++------ .../api/admin/PracticeTestCreateController.java | 5 ++++- .../api/admin/ProblemAdminController.java | 2 ++ .../api/admin/ProblemImageUploadController.java | 8 +++++--- .../api/client/PracticeTestController.java | 3 ++- 8 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/controller/DetailResultApplicationController.java b/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/controller/DetailResultApplicationController.java index e497da5..6677da5 100644 --- a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/controller/DetailResultApplicationController.java +++ b/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/controller/DetailResultApplicationController.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.domain.DetailResultApplication.dto.request.DetailResultApplicationPostRequest; import com.moplus.moplus_server.domain.DetailResultApplication.dto.response.ReviewNoteGetResponse; import com.moplus.moplus_server.domain.DetailResultApplication.service.DetailResultApplicationService; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -11,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Hidden @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/detailResultApplication") @@ -20,7 +22,8 @@ public class DetailResultApplicationController { @PostMapping("") @Operation(summary = "모의고사 결과 상세 분석서 신청하기") - public ResponseEntity createApplication(@RequestBody DetailResultApplicationPostRequest request) { + public ResponseEntity createApplication( + @RequestBody DetailResultApplicationPostRequest request) { detailResultApplicationService.saveApplication(request); ReviewNoteGetResponse reviewNoteInfo = detailResultApplicationService.getReviewNoteInfo(request.testResultId()); return ResponseEntity.ok(reviewNoteInfo); diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/RatingController.java b/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/RatingController.java index 3a89f6e..1612f55 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/RatingController.java +++ b/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/RatingController.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.domain.TestResult.controller; import com.moplus.moplus_server.domain.TestResult.dto.response.RatingGetResponse; +import io.swagger.v3.oas.annotations.Hidden; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -9,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Hidden @RestController @RequestMapping("/api/v1/rating") @RequiredArgsConstructor diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/TestResultController.java b/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/TestResultController.java index 6e4443e..02d0f22 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/TestResultController.java +++ b/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/TestResultController.java @@ -4,6 +4,7 @@ import com.moplus.moplus_server.domain.TestResult.dto.request.SolvingTimePostRequest; import com.moplus.moplus_server.domain.TestResult.dto.response.TestResultGetResponse; import com.moplus.moplus_server.domain.TestResult.service.TestResultService; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import java.util.List; import lombok.RequiredArgsConstructor; @@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Hidden @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/testResult") @@ -25,7 +27,7 @@ public class TestResultController { @PostMapping("/{practiceTestId}/uploadingAnswer") @Operation(summary = "답 입력 결과 제출", description = "테스트 결과지의 ID를 반환합니다.") public ResponseEntity uploadTestAnswers(@PathVariable("practiceTestId") Long id, - @RequestBody List requests) { + @RequestBody List requests) { return ResponseEntity.ok(testResultService.createTestResult(id, requests)); } @@ -37,11 +39,11 @@ public ResponseEntity getTestAnswers(@PathVariable("testR @PostMapping("/{testResultId}/uploadingMinute") @Operation(summary = "풀이 시간 제출 및 시험 결과지 받기", - description = "성적과 풀이시간에 기반한 내 위치 결과지를 반환합니다. 풀이시간은 'PT{시간}H{분}M' 형식으로 보내주세요") + description = "성적과 풀이시간에 기반한 내 위치 결과지를 반환합니다. 풀이시간은 'PT{시간}H{분}M' 형식으로 보내주세요") public ResponseEntity uploadSolvingMinute( - @PathVariable("testResultId") Long id, - @RequestBody SolvingTimePostRequest request - ) { + @PathVariable("testResultId") Long id, + @RequestBody SolvingTimePostRequest request + ) { return ResponseEntity.ok(testResultService.getTestResultBySolvingTime(id, request)); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestAdminController.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestAdminController.java index 1288a85..98f9f8f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestAdminController.java +++ b/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestAdminController.java @@ -1,20 +1,17 @@ package com.moplus.moplus_server.domain.practiceTest.api.admin; import com.moplus.moplus_server.domain.practiceTest.dto.client.response.PracticeTestGetResponse; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; import com.moplus.moplus_server.domain.practiceTest.service.client.PracticeTestService; import com.moplus.moplus_server.domain.practiceTest.service.client.ProblemService; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; -import jakarta.servlet.http.HttpServletRequest; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; +@Hidden @Controller @RequiredArgsConstructor public class PracticeTestAdminController { @@ -32,5 +29,4 @@ public String listPracticeTests(Model model) { } - } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestCreateController.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestCreateController.java index 8766445..1dd5992 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestCreateController.java +++ b/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestCreateController.java @@ -4,6 +4,7 @@ import com.moplus.moplus_server.domain.practiceTest.service.admin.PracticeTestAdminService; import com.moplus.moplus_server.domain.practiceTest.service.client.PracticeTestService; import com.moplus.moplus_server.domain.practiceTest.service.client.ProblemService; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; @@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +@Hidden @Controller @RequestMapping("/admin/practiceTests") @RequiredArgsConstructor @@ -48,7 +50,8 @@ public String submitCreateTestInfo(@ModelAttribute PracticeTestRequest practiceT @PostMapping("/submit/{id}") @Operation(summary = "모의고사 정보 수정 요청") - public String submitUpdateTestInfo(@PathVariable("id") Long id, @ModelAttribute PracticeTestRequest practiceTestRequest, Model model) { + public String submitUpdateTestInfo(@PathVariable("id") Long id, + @ModelAttribute PracticeTestRequest practiceTestRequest, Model model) { practiceTestAdminService.updatePracticeTest(id, practiceTestRequest); practiceTestAdminService.getProblemUpdateModel(model, id); diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemAdminController.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemAdminController.java index 818337c..507b74d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemAdminController.java +++ b/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemAdminController.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; import com.moplus.moplus_server.domain.practiceTest.service.client.PracticeTestService; import com.moplus.moplus_server.domain.practiceTest.service.client.ProblemService; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -12,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +@Hidden @Controller @RequestMapping("/admin/practiceTests") @RequiredArgsConstructor diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemImageUploadController.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemImageUploadController.java index 198e4a2..025033d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemImageUploadController.java +++ b/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemImageUploadController.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.domain.practiceTest.api.admin; import com.moplus.moplus_server.domain.practiceTest.service.admin.ProblemImageUploadService; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; @@ -10,9 +11,9 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +@Hidden @Controller @RequestMapping("/admin/practiceTests") @RequiredArgsConstructor @@ -31,9 +32,10 @@ public String showImageUploadPage(@PathVariable("practiceTestId") Long practiceT @PostMapping("/uploadImage/{problemId}") @Operation(summary = "문제 이미지 업로드 요청") - public String uploadImage(@RequestParam("practiceTestId") Long practiceTestId, @PathVariable("problemId") Long problemId, @RequestParam("image") MultipartFile image) { + public String uploadImage(@RequestParam("practiceTestId") Long practiceTestId, + @PathVariable("problemId") Long problemId, @RequestParam("image") MultipartFile image) { // 이미지 업로드 처리 - problemImageUploadService.uploadImage(practiceTestId ,problemId, image); + problemImageUploadService.uploadImage(practiceTestId, problemId, image); return "redirect:/admin/practiceTests/imageUploadPage/" + practiceTestId; } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/client/PracticeTestController.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/client/PracticeTestController.java index 2db3327..19d0a42 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/client/PracticeTestController.java +++ b/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/client/PracticeTestController.java @@ -1,8 +1,8 @@ package com.moplus.moplus_server.domain.practiceTest.api.client; import com.moplus.moplus_server.domain.practiceTest.dto.client.response.PracticeTestGetResponse; -import com.moplus.moplus_server.domain.practiceTest.service.client.OptimisticLockPracticeTestFacade; import com.moplus.moplus_server.domain.practiceTest.service.client.PracticeTestService; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import java.util.List; import lombok.RequiredArgsConstructor; @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Hidden @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/practiceTests") From 41b4b4e9d6cf3d1750fe7902c7e10d886abeff84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 24 Jan 2025 20:20:05 +0900 Subject: [PATCH 019/115] =?UTF-8?q?[feat/#17]=20=EB=82=B4=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 26 +++++++ .../domain/member/domain/Member.java | 4 +- .../dto/response/MemberGetResponse.java | 18 +++++ .../global/annotation/AuthUser.java | 14 ++++ .../AuthenticationArgumentResolver.java | 53 +++++++++++++ .../global/config/WebConfig.java | 19 +++-- .../global/config/swagger/SwaggerConfig.java | 28 +++++++ .../controller/MemberControllerTest.java | 78 +++++++++++++++++++ ...ion-test.yml => application-mysqltest.yml} | 0 src/test/resources/auth-test-data.sql | 4 +- 10 files changed, 236 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/member/dto/response/MemberGetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/global/annotation/AuthUser.java create mode 100644 src/main/java/com/moplus/moplus_server/global/annotation/AuthenticationArgumentResolver.java create mode 100644 src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java create mode 100644 src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java rename src/test/resources/{application-test.yml => application-mysqltest.yml} (100%) diff --git a/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java b/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java new file mode 100644 index 0000000..c23ddc7 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java @@ -0,0 +1,26 @@ +package com.moplus.moplus_server.domain.member.controller; + +import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.domain.member.dto.response.MemberGetResponse; +import com.moplus.moplus_server.global.annotation.AuthUser; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/member") +@RequiredArgsConstructor +public class MemberController { + + @GetMapping("me") + @Operation(summary = "내 정보 조회", description = "jwt accessToken을 통해 내 정보를 조회합니다.") + public ResponseEntity getMyInfo( + @AuthUser Member member + ) { + return ResponseEntity.ok(MemberGetResponse.of(member)); + } +} + diff --git a/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java b/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java index 289a78d..fb000bd 100644 --- a/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java +++ b/src/main/java/com/moplus/moplus_server/domain/member/domain/Member.java @@ -23,6 +23,7 @@ public class Member extends BaseEntity { @Column(name = "member_id") private Long id; + private String name; private String email; private String password; @@ -30,7 +31,8 @@ public class Member extends BaseEntity { private MemberRole role; @Builder - public Member(String nickname, String email, String password, MemberRole role) { + public Member(String name, String email, String password, MemberRole role) { + this.name = name; this.email = email; this.password = password; this.role = role; diff --git a/src/main/java/com/moplus/moplus_server/domain/member/dto/response/MemberGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/member/dto/response/MemberGetResponse.java new file mode 100644 index 0000000..0585dac --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/member/dto/response/MemberGetResponse.java @@ -0,0 +1,18 @@ +package com.moplus.moplus_server.domain.member.dto.response; + + +import com.moplus.moplus_server.domain.member.domain.Member; + +public record MemberGetResponse( + Long id, + String name, + String email +) { + public static MemberGetResponse of(Member member) { + return new MemberGetResponse( + member.getId(), + member.getName(), + member.getEmail() + ); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/annotation/AuthUser.java b/src/main/java/com/moplus/moplus_server/global/annotation/AuthUser.java new file mode 100644 index 0000000..5dd8a36 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/annotation/AuthUser.java @@ -0,0 +1,14 @@ +package com.moplus.moplus_server.global.annotation; + +import io.swagger.v3.oas.annotations.Parameter; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Parameter(hidden = true) +public @interface AuthUser { + +} diff --git a/src/main/java/com/moplus/moplus_server/global/annotation/AuthenticationArgumentResolver.java b/src/main/java/com/moplus/moplus_server/global/annotation/AuthenticationArgumentResolver.java new file mode 100644 index 0000000..2b20910 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/annotation/AuthenticationArgumentResolver.java @@ -0,0 +1,53 @@ +package com.moplus.moplus_server.global.annotation; + +import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.domain.member.repository.MemberRepository; +import com.moplus.moplus_server.global.error.exception.BusinessException; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +@RequiredArgsConstructor +public class AuthenticationArgumentResolver implements HandlerMethodArgumentResolver { + + private final MemberRepository memberRepository; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + final boolean isUserAuthAnnotation = parameter.getParameterAnnotation(AuthUser.class) != null; + final boolean isMemberClass = parameter.getParameterType().equals(Member.class); + return isUserAuthAnnotation && isMemberClass; + } + + @Override + public Member resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + return getCurrentMember(); + } + + private Member getCurrentMember() { + return memberRepository + .findById(getCurrentMemberId()) + .orElseThrow(() -> new NotFoundException(ErrorCode.MEMBER_NOT_FOUND)); + } + + private Long getCurrentMemberId() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || !authentication.isAuthenticated()) { + throw new BusinessException(ErrorCode.AUTH_NOT_FOUND); + } + + Member principal = (Member) authentication.getPrincipal(); + return principal.getId(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java b/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java index 51117e6..1663f45 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java @@ -1,9 +1,12 @@ package com.moplus.moplus_server.global.config; +import com.moplus.moplus_server.global.annotation.AuthenticationArgumentResolver; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -13,15 +16,21 @@ @EnableTransactionManagement public class WebConfig implements WebMvcConfigurer { + private final AuthenticationArgumentResolver authenticationArgumentResolver; @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("https://dev.mopl.kr","http://dev.mopl.kr", "http://localhost:8080", "https://www.mopl.kr", "http" - + "://localhost:3000", "https://dev-web.mopl.kr", "http://dev-web.mopl.kr") - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") - .allowedHeaders("*") - .allowCredentials(true); + .allowedOrigins("https://dev.mopl.kr", "http://dev.mopl.kr", "http://localhost:8080", + "https://www.mopl.kr", "http" + + "://localhost:3000", "https://dev-web.mopl.kr", "http://dev-web.mopl.kr") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true); } + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(authenticationArgumentResolver); + } } diff --git a/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java b/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java new file mode 100644 index 0000000..f532118 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java @@ -0,0 +1,28 @@ +package com.moplus.moplus_server.global.config.swagger; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +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 org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + private SecurityScheme createAPIKeyScheme() { + return new SecurityScheme().type(SecurityScheme.Type.HTTP) + .bearerFormat("JWT") + .scheme("Bearer"); + } + + @Bean + public OpenAPI openAPI() { + return new OpenAPI().addSecurityItem(new SecurityRequirement().addList("JWT")) + .components(new Components().addSecuritySchemes("JWT", createAPIKeyScheme())) + .info(new Info().title("모플 API 명세서") + .description("모플 API 명세서 입니다") + .version("v0.0.1")); + } +} diff --git a/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java b/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java new file mode 100644 index 0000000..9f0defd --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java @@ -0,0 +1,78 @@ +package com.moplus.moplus_server.domain.member.controller; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.moplus.moplus_server.global.properties.jwt.JwtProperties; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.security.Key; +import java.util.Date; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("h2test") +@Sql({"/auth-test-data.sql"}) +class MemberControllerTest { + + @Nested + class 어드민_로그인 { + + @Autowired + private JwtProperties jwtProperties; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private WebApplicationContext context; + + private String validToken; + private Key key; + + @BeforeEach + public void setMockMvc() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(springSecurity()).build(); + + key = Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes()); + Date issuedAt = new Date(); // 3 hour ago + Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime()); + validToken = Jwts.builder() + .setIssuer(jwtProperties.issuer()) + .setSubject("1") + .claim("role", "ROLE_USER") + .setIssuedAt(issuedAt) + .setExpiration(expiredAt) + .signWith(key) + .compact(); + } + + @Test + void 성공() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/member/me") + .contentType("application/json") + .header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)) + .andExpect(status().isOk()) // 200 응답 확인 + .andExpect(jsonPath("$.id").exists()) // MemberGetResponse의 필드 확인 + .andExpect(jsonPath("$.name").exists()) + .andExpect(jsonPath("$.email").exists()); + } + } +} \ No newline at end of file diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-mysqltest.yml similarity index 100% rename from src/test/resources/application-test.yml rename to src/test/resources/application-mysqltest.yml diff --git a/src/test/resources/auth-test-data.sql b/src/test/resources/auth-test-data.sql index f757a22..595162a 100644 --- a/src/test/resources/auth-test-data.sql +++ b/src/test/resources/auth-test-data.sql @@ -1,4 +1,4 @@ -INSERT INTO member (deleted, created_at, update_at, member_id, email, password, role) +INSERT INTO member (deleted, created_at, update_at, member_id, email, password, name, role) VALUES (false, '2024-07-24 21:27:20.000000', '2024-07-24 21:27:21.000000', 1, 'admin@example.com', - 'password123', 'ADMIN'); + 'password123', '홍길동', 'ADMIN'); From 651afd510d1c2f08967a8770267a588c0efe58d5 Mon Sep 17 00:00:00 2001 From: sejoon Date: Sun, 26 Jan 2025 20:03:57 +0900 Subject: [PATCH 020/115] =?UTF-8?q?[chore/#19]=20=EB=A0=88=EA=B1=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=BD=94=EB=93=9C=20vo=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailResultApplicationPostRequest.java | 18 -- .../dto/response/ProblemGetResponse.java | 30 -- .../DetailResultApplicationRepository.java | 8 - .../request/IncorrectProblemPostRequest.java | 19 -- .../response/IncorrectProblemGetResponse.java | 19 -- .../service/IncorrectProblemService.java | 49 --- .../member/controller/MemberController.java | 3 +- .../admin/request/ProblemImageRequest.java | 20 -- .../dto/admin/request/ProblemPostRequest.java | 7 - .../response/PracticeTestAdminResponse.java | 23 -- .../response/PracticeTestGetResponse.java | 27 -- .../client/response/ProblemGetResponse.java | 26 -- .../repository/ProblemImageRepository.java | 11 - .../repository/ProblemRepository.java | 25 -- .../DetailResultApplicationController.java | 8 +- .../DetailResultApplicationPostRequest.java | 18 ++ .../dto/response/ProblemGetResponse.java | 30 ++ .../dto/response/ReviewNoteGetResponse.java | 10 +- .../entity/DetailResultApplication.java | 2 +- .../DetailResultApplicationRepository.java | 8 + .../DetailResultApplicationService.java | 44 +-- .../controller/RatingController.java | 4 +- .../controller/TestResultController.java | 10 +- .../request/IncorrectProblemPostRequest.java | 19 ++ .../dto/request/SolvingTimePostRequest.java | 6 +- .../response/EstimatedRatingGetResponse.java | 4 +- .../response/IncorrectProblemGetResponse.java | 19 ++ .../dto/response/RatingDetailsResponse.java | 2 +- .../dto/response/RatingGetResponse.java | 2 +- .../dto/response/RatingTableGetResponse.java | 10 +- .../dto/response/TestResultGetResponse.java | 8 +- .../TestResult/entity/EstimatedRating.java | 14 +- .../TestResult/entity/IncorrectProblem.java | 6 +- .../TestResult/entity/TestResult.java | 7 +- .../entity/TestScoreCalculator.java | 10 +- .../repository/EstimatedRatingRepository.java | 4 +- .../IncorrectProblemRepository.java | 4 +- .../repository/TestResultRepository.java | 4 +- .../service/IncorrectProblemService.java | 50 +++ .../TestResult/service/TestResultService.java | 45 +-- .../admin/PracticeTestAdminController.java | 8 +- .../admin/PracticeTestCreateController.java | 10 +- .../api/admin/ProblemAdminController.java | 8 +- .../admin/ProblemImageUploadController.java | 4 +- .../api/client/PracticeTestController.java | 6 +- .../practiceTest/domain/AnswerFormat.java | 8 +- .../practiceTest/domain/FileExtension.java | 2 +- .../practiceTest/domain/PracticeTest.java | 7 +- .../practiceTest/domain/ProblemForTest.java} | 30 +- .../domain/ProblemImageForTest.java} | 10 +- .../practiceTest/domain/ProblemRating.java | 20 +- .../practiceTest/domain/RatingRow.java | 4 +- .../practiceTest/domain/RatingTable.java | 6 +- .../{ => v0}/practiceTest/domain/Subject.java | 18 +- .../admin/request/PracticeTestRequest.java | 15 +- .../admin/request/ProblemCreateRequest.java | 12 +- .../admin/request/ProblemImageRequest.java | 20 ++ .../dto/admin/request/ProblemPostRequest.java | 7 + .../dto/admin/request/RatingTableRequest.java | 9 +- .../response/PracticeTestAdminResponse.java | 23 ++ .../response/PracticeTestGetResponse.java | 27 ++ .../client/response/ProblemGetResponse.java | 26 ++ .../repository/PracticeTestRepository.java | 4 +- .../repository/ProblemImageRepository.java | 10 + .../repository/ProblemRepository.java | 24 ++ .../repository/RatingTableRepository.java | 5 +- .../converter/RatingRowConverter.java | 7 +- .../admin/PracticeTestAdminService.java | 52 +-- .../admin/ProblemImageUploadService.java | 30 +- .../admin/RatingTableAdminService.java | 12 +- .../OptimisticLockPracticeTestFacade.java | 2 +- .../service/client/PracticeTestService.java | 21 +- .../service/client/ProblemService.java | 43 +-- .../global/scheduler/TestResultScheduler.java | 13 +- .../moplus_server/global/utils/s3/S3Util.java | 9 - .../resources/templates/answerInputForm.html | 297 +++++++++--------- .../resources/templates/imageUploadPage.html | 26 +- .../entity/EstimatedRatingTest.java | 6 +- .../controller/MemberControllerTest.java | 2 +- .../client/PracticeTestServiceTest.java | 7 +- .../scheduler/TestResultSchedulerTest.java | 41 ++- 81 files changed, 755 insertions(+), 769 deletions(-) delete mode 100644 src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/request/DetailResultApplicationPostRequest.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/response/ProblemGetResponse.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/respository/DetailResultApplicationRepository.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/TestResult/dto/request/IncorrectProblemPostRequest.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/IncorrectProblemGetResponse.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/TestResult/service/IncorrectProblemService.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemImageRequest.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemPostRequest.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/response/PracticeTestAdminResponse.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/client/response/PracticeTestGetResponse.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/client/response/ProblemGetResponse.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/ProblemImageRepository.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/ProblemRepository.java rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/DetailResultApplication/controller/DetailResultApplicationController.java (74%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/request/DetailResultApplicationPostRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/response/ProblemGetResponse.java rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/DetailResultApplication/dto/response/ReviewNoteGetResponse.java (75%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/DetailResultApplication/entity/DetailResultApplication.java (92%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/respository/DetailResultApplicationRepository.java rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/DetailResultApplication/service/DetailResultApplicationService.java (64%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/controller/RatingController.java (82%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/controller/TestResultController.java (83%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/request/IncorrectProblemPostRequest.java rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/dto/request/SolvingTimePostRequest.java (53%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/dto/response/EstimatedRatingGetResponse.java (69%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/IncorrectProblemGetResponse.java rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/dto/response/RatingDetailsResponse.java (86%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/dto/response/RatingGetResponse.java (79%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/dto/response/RatingTableGetResponse.java (67%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/dto/response/TestResultGetResponse.java (82%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/entity/EstimatedRating.java (86%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/entity/IncorrectProblem.java (90%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/entity/TestResult.java (88%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/entity/TestScoreCalculator.java (72%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/repository/EstimatedRatingRepository.java (63%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/repository/IncorrectProblemRepository.java (63%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/repository/TestResultRepository.java (69%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/TestResult/service/IncorrectProblemService.java rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/TestResult/service/TestResultService.java (76%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/api/admin/PracticeTestAdminController.java (71%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/api/admin/PracticeTestCreateController.java (85%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/api/admin/ProblemAdminController.java (83%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/api/admin/ProblemImageUploadController.java (91%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/api/client/PracticeTestController.java (87%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/domain/AnswerFormat.java (53%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/domain/FileExtension.java (92%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/domain/PracticeTest.java (88%) rename src/main/java/com/moplus/moplus_server/domain/{practiceTest/domain/Problem.java => v0/practiceTest/domain/ProblemForTest.java} (75%) rename src/main/java/com/moplus/moplus_server/domain/{practiceTest/domain/ProblemImage.java => v0/practiceTest/domain/ProblemImageForTest.java} (68%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/domain/ProblemRating.java (53%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/domain/RatingRow.java (93%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/domain/RatingTable.java (88%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/domain/Subject.java (70%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/dto/admin/request/PracticeTestRequest.java (79%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/dto/admin/request/ProblemCreateRequest.java (54%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemImageRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemPostRequest.java rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/dto/admin/request/RatingTableRequest.java (85%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/response/PracticeTestAdminResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/client/response/PracticeTestGetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/client/response/ProblemGetResponse.java rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/repository/PracticeTestRepository.java (84%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemImageRepository.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemRepository.java rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/repository/RatingTableRepository.java (67%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/repository/converter/RatingRowConverter.java (79%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/service/admin/PracticeTestAdminService.java (83%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/service/admin/ProblemImageUploadService.java (77%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/service/admin/RatingTableAdminService.java (75%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/service/client/OptimisticLockPracticeTestFacade.java (88%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/service/client/PracticeTestService.java (64%) rename src/main/java/com/moplus/moplus_server/domain/{ => v0}/practiceTest/service/client/ProblemService.java (57%) diff --git a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/request/DetailResultApplicationPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/request/DetailResultApplicationPostRequest.java deleted file mode 100644 index a94832e..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/request/DetailResultApplicationPostRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.moplus.moplus_server.domain.DetailResultApplication.dto.request; - -import com.moplus.moplus_server.domain.DetailResultApplication.entity.DetailResultApplication; - -public record DetailResultApplicationPostRequest( - Long testResultId, - String name, - String phoneNumber -) { - - public DetailResultApplication toEntity() { - return DetailResultApplication.builder() - .testResultId(testResultId) - .name(name) - .phoneNumber(phoneNumber) - .build(); - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/response/ProblemGetResponse.java deleted file mode 100644 index 4420a72..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/response/ProblemGetResponse.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.moplus.moplus_server.domain.DetailResultApplication.dto.response; - -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; -import com.moplus.moplus_server.domain.practiceTest.domain.ProblemImage; -import com.moplus.moplus_server.domain.practiceTest.domain.ProblemRating; -import lombok.Builder; - -@Builder -public record ProblemGetResponse( - String problemNumber, - String difficultLevel, - double correctRate, - String rating, - String imageUrl -) { - - public static ProblemGetResponse of( - Problem problem - ) { - ProblemRating problemRating = problem.getProblemRating(); - ProblemImage image = problem.getImage(); - return ProblemGetResponse.builder() - .problemNumber(problem.getProblemNumber()) - .difficultLevel(problemRating.getDifficultyLevel()) - .correctRate(problem.getCorrectRate()) - .rating(problemRating.getRating()) - .imageUrl(image.getImageUrl()) - .build(); - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/respository/DetailResultApplicationRepository.java b/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/respository/DetailResultApplicationRepository.java deleted file mode 100644 index 17d4a08..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/respository/DetailResultApplicationRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.moplus.moplus_server.domain.DetailResultApplication.respository; - -import com.moplus.moplus_server.domain.DetailResultApplication.entity.DetailResultApplication; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface DetailResultApplicationRepository extends JpaRepository { - -} diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/request/IncorrectProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/request/IncorrectProblemPostRequest.java deleted file mode 100644 index 438f65a..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/request/IncorrectProblemPostRequest.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.moplus.moplus_server.domain.TestResult.dto.request; - -import com.moplus.moplus_server.domain.TestResult.entity.IncorrectProblem; -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; - -public record IncorrectProblemPostRequest( - String problemNumber, - String incorrectAnswer -) { - - public IncorrectProblem toEntity(Problem problem){ - return IncorrectProblem.builder() - .problemNumber(problemNumber) - .incorrectAnswer(incorrectAnswer) - .point(problem.getPoint()) - .problemId(problem.getId()) - .build(); - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/IncorrectProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/IncorrectProblemGetResponse.java deleted file mode 100644 index 153d0ea..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/IncorrectProblemGetResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.moplus.moplus_server.domain.TestResult.dto.response; - -import com.moplus.moplus_server.domain.TestResult.entity.IncorrectProblem; -import lombok.Builder; - -@Builder -public record IncorrectProblemGetResponse( - String problemNumber, - double correctRate -) { - - public static IncorrectProblemGetResponse from(IncorrectProblem incorrectProblem) { - return IncorrectProblemGetResponse.builder() - .problemNumber(incorrectProblem.getProblemNumber()) - .correctRate(incorrectProblem.getCorrectRate()) - .build(); - } - -} diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/service/IncorrectProblemService.java b/src/main/java/com/moplus/moplus_server/domain/TestResult/service/IncorrectProblemService.java deleted file mode 100644 index 99c24a2..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/service/IncorrectProblemService.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.moplus.moplus_server.domain.TestResult.service; - -import com.moplus.moplus_server.domain.TestResult.dto.request.IncorrectProblemPostRequest; -import com.moplus.moplus_server.domain.TestResult.dto.response.IncorrectProblemGetResponse; -import com.moplus.moplus_server.domain.TestResult.entity.IncorrectProblem; -import com.moplus.moplus_server.domain.TestResult.entity.TestResult; -import com.moplus.moplus_server.domain.TestResult.repository.IncorrectProblemRepository; -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; -import com.moplus.moplus_server.domain.practiceTest.service.client.ProblemService; -import java.util.ArrayList; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class IncorrectProblemService { - - private final IncorrectProblemRepository incorrectProblemRepository; - private final ProblemService problemService; - - public List saveIncorrectProblems( - List requests, - Long practiceTestId, - TestResult testResult) { - List problems = requests.stream() - .map(request -> problemService.getProblemByPracticeTestIdAndNumber(practiceTestId, request.problemNumber())) - .toList(); - - List incorrectProblems = new ArrayList<>(); - for (int i = 0; i < requests.size(); i++) { - Problem matchedProblem = problems.get(i); - IncorrectProblem tempIncorrectProblem = requests.get(i).toEntity(matchedProblem); - - tempIncorrectProblem.setTestResult(testResult); - tempIncorrectProblem.setPracticeTestId(practiceTestId); - tempIncorrectProblem.setCorrectRate(matchedProblem.getCorrectRate()); - IncorrectProblem save = incorrectProblemRepository.save(tempIncorrectProblem); - incorrectProblems.add(save); - } - return incorrectProblems; - } - - public List getResponsesByTestResultId(Long testResultId) { - return incorrectProblemRepository.findAllByTestResultId(testResultId).stream() - .map(IncorrectProblemGetResponse::from) - .toList(); - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java b/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java index c23ddc7..6fd8166 100644 --- a/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java +++ b/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java @@ -22,5 +22,4 @@ public ResponseEntity getMyInfo( ) { return ResponseEntity.ok(MemberGetResponse.of(member)); } -} - +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemImageRequest.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemImageRequest.java deleted file mode 100644 index ef572e7..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemImageRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.moplus.moplus_server.domain.practiceTest.dto.admin.request; - -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; -import lombok.Builder; - -@Builder -public record ProblemImageRequest( - Long problemId, - String problemNumber, - String imageUrl -) { - - public static ProblemImageRequest of(Problem problem) { - return ProblemImageRequest.builder() - .problemId(problem.getId()) - .problemNumber(problem.getProblemNumber()) - .imageUrl(problem.getImage() != null ? problem.getImage().getImageUrl() : null) - .build(); - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemPostRequest.java deleted file mode 100644 index 62026da..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemPostRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.moplus.moplus_server.domain.practiceTest.dto.admin.request; - -public record ProblemPostRequest( - -) { - -} diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/response/PracticeTestAdminResponse.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/response/PracticeTestAdminResponse.java deleted file mode 100644 index d464fa1..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/response/PracticeTestAdminResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.moplus.moplus_server.domain.practiceTest.dto.admin.response; - -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import lombok.Builder; - -@Builder -public record PracticeTestAdminResponse( - Long id, - String name, - String round, - String provider, - String subject -) { - public static PracticeTestAdminResponse from(PracticeTest practiceTest) { - return PracticeTestAdminResponse.builder() - .id(practiceTest.getId()) - .name(practiceTest.getName()) - .provider(practiceTest.getProvider()) - .round(practiceTest.getRound()) - .subject(practiceTest.getSubject().getValue()) - .build(); - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/client/response/PracticeTestGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/client/response/PracticeTestGetResponse.java deleted file mode 100644 index 1dc3d92..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/client/response/PracticeTestGetResponse.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.moplus.moplus_server.domain.practiceTest.dto.client.response; - -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import lombok.Builder; - -@Builder -public record PracticeTestGetResponse( - Long id, - String name, - String round, - String provider, - String subject, - long viewCount, - int totalSolvesCount -) { - public static PracticeTestGetResponse from(PracticeTest practiceTest) { - return PracticeTestGetResponse.builder() - .id(practiceTest.getId()) - .name(practiceTest.getName()) - .provider(practiceTest.getProvider()) - .round(practiceTest.getRound()) - .subject(practiceTest.getSubject().getValue()) - .viewCount(practiceTest.getViewCount()) - .totalSolvesCount(practiceTest.getSolvesCount()) - .build(); - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/client/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/client/response/ProblemGetResponse.java deleted file mode 100644 index 3803c3f..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/client/response/ProblemGetResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.moplus.moplus_server.domain.practiceTest.dto.client.response; - -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; -import lombok.Builder; - -@Builder -public record ProblemGetResponse( - Long id, - String problemNumber, - String answerFormat, - String answer, - int point, - double correctRate -) { - - public static ProblemGetResponse from(Problem problem) { - return ProblemGetResponse.builder() - .id(problem.getId()) - .answer(problem.getAnswer()) - .problemNumber(problem.getProblemNumber()) - .answerFormat(problem.getAnswerFormat().getValue()) - .point(problem.getPoint()) - .correctRate(problem.getCorrectRate()) - .build(); - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/ProblemImageRepository.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/ProblemImageRepository.java deleted file mode 100644 index 353087b..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/ProblemImageRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.moplus.moplus_server.domain.practiceTest.repository; - -import com.moplus.moplus_server.domain.practiceTest.domain.ProblemImage; -import java.util.Optional; -import javax.swing.JPanel; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ProblemImageRepository extends JpaRepository { - - Optional findByProblemId(Long problemId); -} diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/ProblemRepository.java deleted file mode 100644 index cc79a64..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/ProblemRepository.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.moplus.moplus_server.domain.practiceTest.repository; - -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.persistence.LockModeType; -import java.util.List; -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Lock; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -public interface ProblemRepository extends JpaRepository { - - List findAllByPracticeTestId(Long id); - - void deleteAllByPracticeTestId(Long id); - - Optional findByProblemNumberAndPracticeTestId(String problemNumber, Long practiceTest_id); - - @Lock(LockModeType.PESSIMISTIC_WRITE) - @Query("SELECT p FROM Problem p WHERE p.problemNumber = :problem_number AND p.practiceTest.id = :practice_test_id") - Optional findByProblemNumberAndPracticeTestIdWithPessimisticLock(@Param("problem_number") String problemNumber,@Param("practice_test_id") Long practiceTest_id); -} diff --git a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/controller/DetailResultApplicationController.java b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/controller/DetailResultApplicationController.java similarity index 74% rename from src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/controller/DetailResultApplicationController.java rename to src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/controller/DetailResultApplicationController.java index 6677da5..a7edd83 100644 --- a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/controller/DetailResultApplicationController.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/controller/DetailResultApplicationController.java @@ -1,8 +1,8 @@ -package com.moplus.moplus_server.domain.DetailResultApplication.controller; +package com.moplus.moplus_server.domain.v0.DetailResultApplication.controller; -import com.moplus.moplus_server.domain.DetailResultApplication.dto.request.DetailResultApplicationPostRequest; -import com.moplus.moplus_server.domain.DetailResultApplication.dto.response.ReviewNoteGetResponse; -import com.moplus.moplus_server.domain.DetailResultApplication.service.DetailResultApplicationService; +import com.moplus.moplus_server.domain.v0.DetailResultApplication.dto.request.DetailResultApplicationPostRequest; +import com.moplus.moplus_server.domain.v0.DetailResultApplication.dto.response.ReviewNoteGetResponse; +import com.moplus.moplus_server.domain.v0.DetailResultApplication.service.DetailResultApplicationService; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/request/DetailResultApplicationPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/request/DetailResultApplicationPostRequest.java new file mode 100644 index 0000000..2440e76 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/request/DetailResultApplicationPostRequest.java @@ -0,0 +1,18 @@ +package com.moplus.moplus_server.domain.v0.DetailResultApplication.dto.request; + +import com.moplus.moplus_server.domain.v0.DetailResultApplication.entity.DetailResultApplication; + +public record DetailResultApplicationPostRequest( + Long testResultId, + String name, + String phoneNumber +) { + + public DetailResultApplication toEntity() { + return DetailResultApplication.builder() + .testResultId(testResultId) + .name(name) + .phoneNumber(phoneNumber) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/response/ProblemGetResponse.java new file mode 100644 index 0000000..7fb14eb --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/response/ProblemGetResponse.java @@ -0,0 +1,30 @@ +package com.moplus.moplus_server.domain.v0.DetailResultApplication.dto.response; + +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemImageForTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemRating; +import lombok.Builder; + +@Builder +public record ProblemGetResponse( + String problemNumber, + String difficultLevel, + double correctRate, + String rating, + String imageUrl +) { + + public static ProblemGetResponse of( + ProblemForTest problemForTest + ) { + ProblemRating problemRating = problemForTest.getProblemRating(); + ProblemImageForTest image = problemForTest.getImage(); + return ProblemGetResponse.builder() + .problemNumber(problemForTest.getProblemNumber()) + .difficultLevel(problemRating.getDifficultyLevel()) + .correctRate(problemForTest.getCorrectRate()) + .rating(problemRating.getRating()) + .imageUrl(image.getImageUrl()) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/response/ReviewNoteGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/response/ReviewNoteGetResponse.java similarity index 75% rename from src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/response/ReviewNoteGetResponse.java rename to src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/response/ReviewNoteGetResponse.java index 95c8674..9fe3e97 100644 --- a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/dto/response/ReviewNoteGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/dto/response/ReviewNoteGetResponse.java @@ -1,10 +1,8 @@ -package com.moplus.moplus_server.domain.DetailResultApplication.dto.response; +package com.moplus.moplus_server.domain.v0.DetailResultApplication.dto.response; -import com.moplus.moplus_server.domain.TestResult.dto.response.EstimatedRatingGetResponse; -import com.moplus.moplus_server.domain.TestResult.dto.response.IncorrectProblemGetResponse; -import com.moplus.moplus_server.domain.TestResult.dto.response.RatingTableGetResponse; -import com.moplus.moplus_server.domain.TestResult.dto.response.TestResultGetResponse; -import com.moplus.moplus_server.domain.TestResult.entity.TestResult; +import com.moplus.moplus_server.domain.v0.TestResult.dto.response.EstimatedRatingGetResponse; +import com.moplus.moplus_server.domain.v0.TestResult.dto.response.IncorrectProblemGetResponse; +import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; import java.time.Duration; import java.util.List; import lombok.Builder; diff --git a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/entity/DetailResultApplication.java b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/entity/DetailResultApplication.java similarity index 92% rename from src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/entity/DetailResultApplication.java rename to src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/entity/DetailResultApplication.java index 0f9df6a..0b35102 100644 --- a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/entity/DetailResultApplication.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/entity/DetailResultApplication.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.DetailResultApplication.entity; +package com.moplus.moplus_server.domain.v0.DetailResultApplication.entity; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.Column; diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/respository/DetailResultApplicationRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/respository/DetailResultApplicationRepository.java new file mode 100644 index 0000000..b723fc9 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/respository/DetailResultApplicationRepository.java @@ -0,0 +1,8 @@ +package com.moplus.moplus_server.domain.v0.DetailResultApplication.respository; + +import com.moplus.moplus_server.domain.v0.DetailResultApplication.entity.DetailResultApplication; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DetailResultApplicationRepository extends JpaRepository { + +} diff --git a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/service/DetailResultApplicationService.java b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java similarity index 64% rename from src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/service/DetailResultApplicationService.java rename to src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java index 9765fe3..085cfad 100644 --- a/src/main/java/com/moplus/moplus_server/domain/DetailResultApplication/service/DetailResultApplicationService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java @@ -1,20 +1,20 @@ -package com.moplus.moplus_server.domain.DetailResultApplication.service; +package com.moplus.moplus_server.domain.v0.DetailResultApplication.service; -import com.moplus.moplus_server.domain.DetailResultApplication.dto.request.DetailResultApplicationPostRequest; -import com.moplus.moplus_server.domain.DetailResultApplication.dto.response.ProblemGetResponse; -import com.moplus.moplus_server.domain.DetailResultApplication.dto.response.ReviewNoteGetResponse; -import com.moplus.moplus_server.domain.DetailResultApplication.respository.DetailResultApplicationRepository; -import com.moplus.moplus_server.domain.TestResult.dto.response.EstimatedRatingGetResponse; -import com.moplus.moplus_server.domain.TestResult.entity.IncorrectProblem; -import com.moplus.moplus_server.domain.TestResult.entity.TestResult; -import com.moplus.moplus_server.domain.TestResult.repository.EstimatedRatingRepository; -import com.moplus.moplus_server.domain.TestResult.repository.IncorrectProblemRepository; -import com.moplus.moplus_server.domain.TestResult.repository.TestResultRepository; -import com.moplus.moplus_server.domain.TestResult.service.IncorrectProblemService; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; -import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.DetailResultApplication.dto.request.DetailResultApplicationPostRequest; +import com.moplus.moplus_server.domain.v0.DetailResultApplication.dto.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.v0.DetailResultApplication.dto.response.ReviewNoteGetResponse; +import com.moplus.moplus_server.domain.v0.DetailResultApplication.respository.DetailResultApplicationRepository; +import com.moplus.moplus_server.domain.v0.TestResult.dto.response.EstimatedRatingGetResponse; +import com.moplus.moplus_server.domain.v0.TestResult.entity.IncorrectProblem; +import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; +import com.moplus.moplus_server.domain.v0.TestResult.repository.EstimatedRatingRepository; +import com.moplus.moplus_server.domain.v0.TestResult.repository.IncorrectProblemRepository; +import com.moplus.moplus_server.domain.v0.TestResult.repository.TestResultRepository; +import com.moplus.moplus_server.domain.v0.TestResult.service.IncorrectProblemService; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.time.Duration; @@ -47,28 +47,30 @@ public ReviewNoteGetResponse getReviewNoteInfo(Long testResultId) { PracticeTest practiceTest = getPracticeTestById(testResult.getPracticeTestId()); Duration averageSolvingTime = practiceTest.getAverageSolvingTime(); - List estimatedRatingGetResponses = estimatedRatingRepository.findAllByTestResultId(testResultId).stream() + List estimatedRatingGetResponses = estimatedRatingRepository.findAllByTestResultId( + testResultId).stream() .map(EstimatedRatingGetResponse::from) .toList(); int 대성마이맥_rating = estimatedRatingGetResponses.get(0).estimatedRating(); - List incorrectProblems = incorrectProblemRepository.findAllByTestResultId(testResultId).stream() + List incorrectProblemForTests = incorrectProblemRepository.findAllByTestResultId(testResultId) + .stream() .map(IncorrectProblem::getProblemId) .map(problemId -> problemRepository.findById(problemId).orElseThrow()) .toList(); - List forCurrentRating = incorrectProblems.stream() + List forCurrentRating = incorrectProblemForTests.stream() .filter(problem -> problem.getProblemRating().getRatingValue() == 대성마이맥_rating) .map(ProblemGetResponse::of) .toList(); - List forNextRating = incorrectProblems.stream() + List forNextRating = incorrectProblemForTests.stream() .filter(problem -> problem.getProblemRating().getRatingValue() == 대성마이맥_rating - 1) .map(ProblemGetResponse::of) .toList(); - List forBeforeRating = incorrectProblems.stream() + List forBeforeRating = incorrectProblemForTests.stream() .filter(problem -> problem.getProblemRating().getRatingValue() >= 대성마이맥_rating + 1) .map(ProblemGetResponse::of) .toList(); diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/RatingController.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/controller/RatingController.java similarity index 82% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/controller/RatingController.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/controller/RatingController.java index 1612f55..d95bd4c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/RatingController.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/controller/RatingController.java @@ -1,6 +1,6 @@ -package com.moplus.moplus_server.domain.TestResult.controller; +package com.moplus.moplus_server.domain.v0.TestResult.controller; -import com.moplus.moplus_server.domain.TestResult.dto.response.RatingGetResponse; +import com.moplus.moplus_server.domain.v0.TestResult.dto.response.RatingGetResponse; import io.swagger.v3.oas.annotations.Hidden; import java.util.List; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/TestResultController.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/controller/TestResultController.java similarity index 83% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/controller/TestResultController.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/controller/TestResultController.java index 02d0f22..4c0ae4b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/controller/TestResultController.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/controller/TestResultController.java @@ -1,9 +1,9 @@ -package com.moplus.moplus_server.domain.TestResult.controller; +package com.moplus.moplus_server.domain.v0.TestResult.controller; -import com.moplus.moplus_server.domain.TestResult.dto.request.IncorrectProblemPostRequest; -import com.moplus.moplus_server.domain.TestResult.dto.request.SolvingTimePostRequest; -import com.moplus.moplus_server.domain.TestResult.dto.response.TestResultGetResponse; -import com.moplus.moplus_server.domain.TestResult.service.TestResultService; +import com.moplus.moplus_server.domain.v0.TestResult.dto.request.IncorrectProblemPostRequest; +import com.moplus.moplus_server.domain.v0.TestResult.dto.request.SolvingTimePostRequest; +import com.moplus.moplus_server.domain.v0.TestResult.dto.response.TestResultGetResponse; +import com.moplus.moplus_server.domain.v0.TestResult.service.TestResultService; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import java.util.List; diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/request/IncorrectProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/request/IncorrectProblemPostRequest.java new file mode 100644 index 0000000..6a97f81 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/request/IncorrectProblemPostRequest.java @@ -0,0 +1,19 @@ +package com.moplus.moplus_server.domain.v0.TestResult.dto.request; + +import com.moplus.moplus_server.domain.v0.TestResult.entity.IncorrectProblem; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; + +public record IncorrectProblemPostRequest( + String problemNumber, + String incorrectAnswer +) { + + public IncorrectProblem toEntity(ProblemForTest problemForTest) { + return IncorrectProblem.builder() + .problemNumber(problemNumber) + .incorrectAnswer(incorrectAnswer) + .point(problemForTest.getPoint()) + .problemId(problemForTest.getId()) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/request/SolvingTimePostRequest.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/request/SolvingTimePostRequest.java similarity index 53% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/dto/request/SolvingTimePostRequest.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/request/SolvingTimePostRequest.java index 3ef3a47..e10404d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/request/SolvingTimePostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/request/SolvingTimePostRequest.java @@ -1,12 +1,12 @@ -package com.moplus.moplus_server.domain.TestResult.dto.request; +package com.moplus.moplus_server.domain.v0.TestResult.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import java.time.Duration; import java.time.LocalTime; public record SolvingTimePostRequest( - @Schema(example = "PT1H10M") - String solvingTime + @Schema(example = "PT1H10M") + String solvingTime ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/EstimatedRatingGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/EstimatedRatingGetResponse.java similarity index 69% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/EstimatedRatingGetResponse.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/EstimatedRatingGetResponse.java index c202404..d7ac54c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/EstimatedRatingGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/EstimatedRatingGetResponse.java @@ -1,6 +1,6 @@ -package com.moplus.moplus_server.domain.TestResult.dto.response; +package com.moplus.moplus_server.domain.v0.TestResult.dto.response; -import com.moplus.moplus_server.domain.TestResult.entity.EstimatedRating; +import com.moplus.moplus_server.domain.v0.TestResult.entity.EstimatedRating; public record EstimatedRatingGetResponse( String ratingProvider, diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/IncorrectProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/IncorrectProblemGetResponse.java new file mode 100644 index 0000000..c34c9c4 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/IncorrectProblemGetResponse.java @@ -0,0 +1,19 @@ +package com.moplus.moplus_server.domain.v0.TestResult.dto.response; + +import com.moplus.moplus_server.domain.v0.TestResult.entity.IncorrectProblem; +import lombok.Builder; + +@Builder +public record IncorrectProblemGetResponse( + String problemNumber, + double correctRate +) { + + public static IncorrectProblemGetResponse from(IncorrectProblem incorrectProblem) { + return IncorrectProblemGetResponse.builder() + .problemNumber(incorrectProblem.getProblemNumber()) + .correctRate(incorrectProblem.getCorrectRate()) + .build(); + } + +} diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/RatingDetailsResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/RatingDetailsResponse.java similarity index 86% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/RatingDetailsResponse.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/RatingDetailsResponse.java index 0ad15bd..5565f29 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/RatingDetailsResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/RatingDetailsResponse.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.TestResult.dto.response; +package com.moplus.moplus_server.domain.v0.TestResult.dto.response; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/RatingGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/RatingGetResponse.java similarity index 79% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/RatingGetResponse.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/RatingGetResponse.java index fb25cb2..0d5f4df 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/RatingGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/RatingGetResponse.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.TestResult.dto.response; +package com.moplus.moplus_server.domain.v0.TestResult.dto.response; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/RatingTableGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/RatingTableGetResponse.java similarity index 67% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/RatingTableGetResponse.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/RatingTableGetResponse.java index d99082e..7952c28 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/RatingTableGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/RatingTableGetResponse.java @@ -1,7 +1,7 @@ -package com.moplus.moplus_server.domain.TestResult.dto.response; +package com.moplus.moplus_server.domain.v0.TestResult.dto.response; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingRow; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingTable; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingRow; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingTable; import java.util.List; import lombok.Builder; @@ -10,9 +10,9 @@ public record RatingTableGetResponse( Long id, Long practiceId, String ratingProvider, - ListratingRows + List ratingRows ) { - public static RatingTableGetResponse from(RatingTable ratingTable){ + public static RatingTableGetResponse from(RatingTable ratingTable) { return RatingTableGetResponse.builder() .id(ratingTable.getId()) .practiceId(ratingTable.getPracticeTestId()) diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/TestResultGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/TestResultGetResponse.java similarity index 82% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/TestResultGetResponse.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/TestResultGetResponse.java index 6b0a41a..564e8c6 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/dto/response/TestResultGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/dto/response/TestResultGetResponse.java @@ -1,8 +1,6 @@ -package com.moplus.moplus_server.domain.TestResult.dto.response; +package com.moplus.moplus_server.domain.v0.TestResult.dto.response; -import com.moplus.moplus_server.domain.TestResult.entity.EstimatedRating; -import com.moplus.moplus_server.domain.TestResult.entity.TestResult; -import io.swagger.v3.oas.annotations.media.Schema; +import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; import java.time.Duration; import java.util.List; import lombok.Builder; @@ -24,7 +22,7 @@ public static TestResultGetResponse of( List estimatedRatingGetResponses, List incorrectProblems, List ratingTables -) { + ) { return TestResultGetResponse.builder() .testResultId(testResult.getId()) .score(testResult.getScore()) diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/entity/EstimatedRating.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/EstimatedRating.java similarity index 86% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/entity/EstimatedRating.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/EstimatedRating.java index b71238e..c8c9c08 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/entity/EstimatedRating.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/EstimatedRating.java @@ -1,7 +1,7 @@ -package com.moplus.moplus_server.domain.TestResult.entity; +package com.moplus.moplus_server.domain.v0.TestResult.entity; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingRow; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingTable; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingRow; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingTable; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -27,15 +27,15 @@ public class EstimatedRating { private String ratingProvider; - public EstimatedRating(int estimatedRating, Long testResultId,String ratingProvider) { + public EstimatedRating(int estimatedRating, Long testResultId, String ratingProvider) { this.estimatedRating = estimatedRating; this.testResultId = testResultId; this.ratingProvider = ratingProvider; } - public static EstimatedRating of(int testScore, Long testResultId,RatingTable ratingTables) { + public static EstimatedRating of(int testScore, Long testResultId, RatingTable ratingTables) { int estimatedRating = calculateEstimatedRating(testScore, ratingTables.getRatingRows()); - return new EstimatedRating(estimatedRating, testResultId,ratingTables.getRatingProvider()); + return new EstimatedRating(estimatedRating, testResultId, ratingTables.getRatingProvider()); } private static int calculateEstimatedRating(int testScore, List ratingRows) { @@ -47,7 +47,7 @@ private static int calculateEstimatedRating(int testScore, List ratin int[] scoreRange = parseRawScores(rawScores); int min = scoreRange[0]; int max = scoreRange[1]; - if(testScore >= min){ + if (testScore >= min) { return i + 1; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/entity/IncorrectProblem.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/IncorrectProblem.java similarity index 90% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/entity/IncorrectProblem.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/IncorrectProblem.java index 2a8de67..08dcfb9 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/entity/IncorrectProblem.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/IncorrectProblem.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.TestResult.entity; +package com.moplus.moplus_server.domain.v0.TestResult.entity; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.Column; @@ -36,8 +36,8 @@ public class IncorrectProblem extends BaseEntity { @Builder public IncorrectProblem(Long problemId, Long practiceTestId, String incorrectAnswer, String problemNumber, - int point, - TestResult testResult, double correctRate) { + int point, + TestResult testResult, double correctRate) { this.problemId = problemId; this.practiceTestId = practiceTestId; this.incorrectAnswer = incorrectAnswer; diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/entity/TestResult.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/TestResult.java similarity index 88% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/entity/TestResult.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/TestResult.java index 7632937..d0e096b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/entity/TestResult.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/TestResult.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.TestResult.entity; +package com.moplus.moplus_server.domain.v0.TestResult.entity; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.Column; @@ -7,7 +7,6 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import java.time.Duration; -import java.time.LocalTime; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -37,8 +36,8 @@ public TestResult(int score, Duration solvingTime, Long practiceTestId) { public static TestResult fromPracticeTestId(Long practiceTestId) { return TestResult.builder() - .practiceTestId(practiceTestId) - .build(); + .practiceTestId(practiceTestId) + .build(); } public void addScore(int score) { diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/entity/TestScoreCalculator.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/TestScoreCalculator.java similarity index 72% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/entity/TestScoreCalculator.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/TestScoreCalculator.java index 6332c98..291806d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/entity/TestScoreCalculator.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/entity/TestScoreCalculator.java @@ -1,6 +1,6 @@ -package com.moplus.moplus_server.domain.TestResult.entity; +package com.moplus.moplus_server.domain.v0.TestResult.entity; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.util.List; @@ -11,10 +11,10 @@ @RequiredArgsConstructor public class TestScoreCalculator { - public int calculateScore(List incorrectProblems, PracticeTest practiceTest){ + public int calculateScore(List incorrectProblems, PracticeTest practiceTest) { int minusPoint = incorrectProblems.stream() - .mapToInt(IncorrectProblem::getPoint) - .sum(); + .mapToInt(IncorrectProblem::getPoint) + .sum(); int score = practiceTest.getSubject().getPerfectScore() - minusPoint; diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/repository/EstimatedRatingRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/repository/EstimatedRatingRepository.java similarity index 63% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/repository/EstimatedRatingRepository.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/repository/EstimatedRatingRepository.java index 183cb4d..76e67e8 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/repository/EstimatedRatingRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/repository/EstimatedRatingRepository.java @@ -1,6 +1,6 @@ -package com.moplus.moplus_server.domain.TestResult.repository; +package com.moplus.moplus_server.domain.v0.TestResult.repository; -import com.moplus.moplus_server.domain.TestResult.entity.EstimatedRating; +import com.moplus.moplus_server.domain.v0.TestResult.entity.EstimatedRating; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/repository/IncorrectProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/repository/IncorrectProblemRepository.java similarity index 63% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/repository/IncorrectProblemRepository.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/repository/IncorrectProblemRepository.java index db92950..d8059db 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/repository/IncorrectProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/repository/IncorrectProblemRepository.java @@ -1,6 +1,6 @@ -package com.moplus.moplus_server.domain.TestResult.repository; +package com.moplus.moplus_server.domain.v0.TestResult.repository; -import com.moplus.moplus_server.domain.TestResult.entity.IncorrectProblem; +import com.moplus.moplus_server.domain.v0.TestResult.entity.IncorrectProblem; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/repository/TestResultRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/repository/TestResultRepository.java similarity index 69% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/repository/TestResultRepository.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/repository/TestResultRepository.java index 3eafb2d..2d5a3c1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/repository/TestResultRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/repository/TestResultRepository.java @@ -1,6 +1,6 @@ -package com.moplus.moplus_server.domain.TestResult.repository; +package com.moplus.moplus_server.domain.v0.TestResult.repository; -import com.moplus.moplus_server.domain.TestResult.entity.TestResult; +import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/service/IncorrectProblemService.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/service/IncorrectProblemService.java new file mode 100644 index 0000000..7eb3f45 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/service/IncorrectProblemService.java @@ -0,0 +1,50 @@ +package com.moplus.moplus_server.domain.v0.TestResult.service; + +import com.moplus.moplus_server.domain.v0.TestResult.dto.request.IncorrectProblemPostRequest; +import com.moplus.moplus_server.domain.v0.TestResult.dto.response.IncorrectProblemGetResponse; +import com.moplus.moplus_server.domain.v0.TestResult.entity.IncorrectProblem; +import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; +import com.moplus.moplus_server.domain.v0.TestResult.repository.IncorrectProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; +import com.moplus.moplus_server.domain.v0.practiceTest.service.client.ProblemService; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class IncorrectProblemService { + + private final IncorrectProblemRepository incorrectProblemRepository; + private final ProblemService problemService; + + public List saveIncorrectProblems( + List requests, + Long practiceTestId, + TestResult testResult) { + List problemForTests = requests.stream() + .map(request -> problemService.getProblemByPracticeTestIdAndNumber(practiceTestId, + request.problemNumber())) + .toList(); + + List incorrectProblems = new ArrayList<>(); + for (int i = 0; i < requests.size(); i++) { + ProblemForTest matchedProblemForTest = problemForTests.get(i); + IncorrectProblem tempIncorrectProblem = requests.get(i).toEntity(matchedProblemForTest); + + tempIncorrectProblem.setTestResult(testResult); + tempIncorrectProblem.setPracticeTestId(practiceTestId); + tempIncorrectProblem.setCorrectRate(matchedProblemForTest.getCorrectRate()); + IncorrectProblem save = incorrectProblemRepository.save(tempIncorrectProblem); + incorrectProblems.add(save); + } + return incorrectProblems; + } + + public List getResponsesByTestResultId(Long testResultId) { + return incorrectProblemRepository.findAllByTestResultId(testResultId).stream() + .map(IncorrectProblemGetResponse::from) + .toList(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/TestResult/service/TestResultService.java b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/service/TestResultService.java similarity index 76% rename from src/main/java/com/moplus/moplus_server/domain/TestResult/service/TestResultService.java rename to src/main/java/com/moplus/moplus_server/domain/v0/TestResult/service/TestResultService.java index 6dce0ef..337670f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/TestResult/service/TestResultService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/TestResult/service/TestResultService.java @@ -1,20 +1,20 @@ -package com.moplus.moplus_server.domain.TestResult.service; - -import com.moplus.moplus_server.domain.TestResult.dto.request.IncorrectProblemPostRequest; -import com.moplus.moplus_server.domain.TestResult.dto.request.SolvingTimePostRequest; -import com.moplus.moplus_server.domain.TestResult.dto.response.EstimatedRatingGetResponse; -import com.moplus.moplus_server.domain.TestResult.dto.response.RatingTableGetResponse; -import com.moplus.moplus_server.domain.TestResult.dto.response.TestResultGetResponse; -import com.moplus.moplus_server.domain.TestResult.entity.EstimatedRating; -import com.moplus.moplus_server.domain.TestResult.entity.IncorrectProblem; -import com.moplus.moplus_server.domain.TestResult.entity.TestResult; -import com.moplus.moplus_server.domain.TestResult.entity.TestScoreCalculator; -import com.moplus.moplus_server.domain.TestResult.repository.EstimatedRatingRepository; -import com.moplus.moplus_server.domain.TestResult.repository.TestResultRepository; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingTable; -import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.practiceTest.repository.RatingTableRepository; +package com.moplus.moplus_server.domain.v0.TestResult.service; + +import com.moplus.moplus_server.domain.v0.TestResult.dto.request.IncorrectProblemPostRequest; +import com.moplus.moplus_server.domain.v0.TestResult.dto.request.SolvingTimePostRequest; +import com.moplus.moplus_server.domain.v0.TestResult.dto.response.EstimatedRatingGetResponse; +import com.moplus.moplus_server.domain.v0.TestResult.dto.response.RatingTableGetResponse; +import com.moplus.moplus_server.domain.v0.TestResult.dto.response.TestResultGetResponse; +import com.moplus.moplus_server.domain.v0.TestResult.entity.EstimatedRating; +import com.moplus.moplus_server.domain.v0.TestResult.entity.IncorrectProblem; +import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; +import com.moplus.moplus_server.domain.v0.TestResult.entity.TestScoreCalculator; +import com.moplus.moplus_server.domain.v0.TestResult.repository.EstimatedRatingRepository; +import com.moplus.moplus_server.domain.v0.TestResult.repository.TestResultRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingTable; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.RatingTableRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.time.Duration; @@ -34,6 +34,10 @@ public class TestResultService { private final RatingTableRepository ratingTableRepository; private final EstimatedRatingRepository estimatedRatingRepository; + private static boolean hasRawScore(RatingTable ratingTable) { + return !ratingTable.getRatingRows().get(0).getRawScores().isBlank(); + } + @Transactional public Long createTestResult(Long practiceTestId, List requests) { PracticeTest practiceTest = getPracticeTestById(practiceTestId); @@ -64,7 +68,8 @@ public TestResultGetResponse getTestResult(Long testResultId) { List ratingTableGetResponses = getRatingTableGetResponses( practiceTest); - List estimatedRatingGetResponses = estimatedRatingRepository.findAllByTestResultId(testResultId).stream() + List estimatedRatingGetResponses = estimatedRatingRepository.findAllByTestResultId( + testResultId).stream() .map(EstimatedRatingGetResponse::from) .toList(); @@ -115,8 +120,4 @@ private List getRatingTableGetResponses(PracticeTest pra .map(RatingTableGetResponse::from) .toList(); } - - private static boolean hasRawScore(RatingTable ratingTable) { - return !ratingTable.getRatingRows().get(0).getRawScores().isBlank(); - } } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestAdminController.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/PracticeTestAdminController.java similarity index 71% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestAdminController.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/PracticeTestAdminController.java index 98f9f8f..473b320 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestAdminController.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/PracticeTestAdminController.java @@ -1,8 +1,8 @@ -package com.moplus.moplus_server.domain.practiceTest.api.admin; +package com.moplus.moplus_server.domain.v0.practiceTest.api.admin; -import com.moplus.moplus_server.domain.practiceTest.dto.client.response.PracticeTestGetResponse; -import com.moplus.moplus_server.domain.practiceTest.service.client.PracticeTestService; -import com.moplus.moplus_server.domain.practiceTest.service.client.ProblemService; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.client.response.PracticeTestGetResponse; +import com.moplus.moplus_server.domain.v0.practiceTest.service.client.PracticeTestService; +import com.moplus.moplus_server.domain.v0.practiceTest.service.client.ProblemService; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import java.util.List; diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestCreateController.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/PracticeTestCreateController.java similarity index 85% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestCreateController.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/PracticeTestCreateController.java index 1dd5992..75b9588 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/PracticeTestCreateController.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/PracticeTestCreateController.java @@ -1,9 +1,9 @@ -package com.moplus.moplus_server.domain.practiceTest.api.admin; +package com.moplus.moplus_server.domain.v0.practiceTest.api.admin; -import com.moplus.moplus_server.domain.practiceTest.dto.admin.request.PracticeTestRequest; -import com.moplus.moplus_server.domain.practiceTest.service.admin.PracticeTestAdminService; -import com.moplus.moplus_server.domain.practiceTest.service.client.PracticeTestService; -import com.moplus.moplus_server.domain.practiceTest.service.client.ProblemService; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.PracticeTestRequest; +import com.moplus.moplus_server.domain.v0.practiceTest.service.admin.PracticeTestAdminService; +import com.moplus.moplus_server.domain.v0.practiceTest.service.client.PracticeTestService; +import com.moplus.moplus_server.domain.v0.practiceTest.service.client.ProblemService; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemAdminController.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/ProblemAdminController.java similarity index 83% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemAdminController.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/ProblemAdminController.java index 507b74d..43211a5 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemAdminController.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/ProblemAdminController.java @@ -1,8 +1,8 @@ -package com.moplus.moplus_server.domain.practiceTest.api.admin; +package com.moplus.moplus_server.domain.v0.practiceTest.api.admin; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.service.client.PracticeTestService; -import com.moplus.moplus_server.domain.practiceTest.service.client.ProblemService; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.service.client.PracticeTestService; +import com.moplus.moplus_server.domain.v0.practiceTest.service.client.ProblemService; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemImageUploadController.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/ProblemImageUploadController.java similarity index 91% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemImageUploadController.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/ProblemImageUploadController.java index 025033d..db75303 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/admin/ProblemImageUploadController.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/admin/ProblemImageUploadController.java @@ -1,6 +1,6 @@ -package com.moplus.moplus_server.domain.practiceTest.api.admin; +package com.moplus.moplus_server.domain.v0.practiceTest.api.admin; -import com.moplus.moplus_server.domain.practiceTest.service.admin.ProblemImageUploadService; +import com.moplus.moplus_server.domain.v0.practiceTest.service.admin.ProblemImageUploadService; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/client/PracticeTestController.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/client/PracticeTestController.java similarity index 87% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/api/client/PracticeTestController.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/client/PracticeTestController.java index 19d0a42..9dab596 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/api/client/PracticeTestController.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/api/client/PracticeTestController.java @@ -1,7 +1,7 @@ -package com.moplus.moplus_server.domain.practiceTest.api.client; +package com.moplus.moplus_server.domain.v0.practiceTest.api.client; -import com.moplus.moplus_server.domain.practiceTest.dto.client.response.PracticeTestGetResponse; -import com.moplus.moplus_server.domain.practiceTest.service.client.PracticeTestService; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.client.response.PracticeTestGetResponse; +import com.moplus.moplus_server.domain.v0.practiceTest.service.client.PracticeTestService; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import java.util.List; diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/AnswerFormat.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/AnswerFormat.java similarity index 53% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/AnswerFormat.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/AnswerFormat.java index c7bcc81..3c71f3b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/AnswerFormat.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/AnswerFormat.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.practiceTest.domain; +package com.moplus.moplus_server.domain.v0.practiceTest.domain; import java.util.Arrays; import lombok.AllArgsConstructor; @@ -15,8 +15,8 @@ public enum AnswerFormat { public static AnswerFormat fromValue(String value) { return Arrays.stream(AnswerFormat.values()) - .filter(answerFormat -> answerFormat.value.equals(value)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("해당 값에 맞는 answerFormat가 없습니다: " + value)); + .filter(answerFormat -> answerFormat.value.equals(value)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("해당 값에 맞는 answerFormat가 없습니다: " + value)); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/FileExtension.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/FileExtension.java similarity index 92% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/FileExtension.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/FileExtension.java index 2111311..256a283 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/FileExtension.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/FileExtension.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.practiceTest.domain; +package com.moplus.moplus_server.domain.v0.practiceTest.domain; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/PracticeTest.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java similarity index 88% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/PracticeTest.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java index c54fe51..d40720a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/PracticeTest.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java @@ -1,6 +1,6 @@ -package com.moplus.moplus_server.domain.practiceTest.domain; +package com.moplus.moplus_server.domain.v0.practiceTest.domain; -import com.moplus.moplus_server.domain.practiceTest.dto.admin.request.PracticeTestRequest; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.PracticeTestRequest; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -9,7 +9,6 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.Version; import java.time.Duration; import lombok.Builder; import lombok.Getter; @@ -47,7 +46,7 @@ public PracticeTest(String name, String round, String provider, String publicati this.solvesCount = 0; this.publicationYear = publicationYear; this.subject = subject; - this.averageSolvingTime = Duration.ZERO; + this.averageSolvingTime = Duration.ZERO; } public void updateByPracticeTestRequest(PracticeTestRequest request) { diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/Problem.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/ProblemForTest.java similarity index 75% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/Problem.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/ProblemForTest.java index 79dcec7..f72fd36 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/ProblemForTest.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.practiceTest.domain; +package com.moplus.moplus_server.domain.v0.practiceTest.domain; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.CascadeType; @@ -20,12 +20,14 @@ @Getter @Entity @NoArgsConstructor -public class Problem extends BaseEntity { +public class ProblemForTest extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "problem_id") + @Column(name = "problem_for_test_id") Long id; - + double correctRate; + @Enumerated(EnumType.STRING) + ProblemRating problemRating; private String problemNumber; @Enumerated(EnumType.STRING) private AnswerFormat answerFormat; @@ -35,23 +37,19 @@ public class Problem extends BaseEntity { private String conceptType; private String unit; private String subunit; - double correctRate; - - @Enumerated(EnumType.STRING) - ProblemRating problemRating; - @ManyToOne() @JoinColumn(name = "practice_test_id") private PracticeTest practiceTest; @OneToOne(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, orphanRemoval = true) @JoinColumn(name = "problem_image_id") - private ProblemImage image; + private ProblemImageForTest image; @Builder - public Problem(String problemNumber, AnswerFormat answerFormat, String answer, int point, Long incorrectNum, - String conceptType, String unit, String subunit, double correctRate, ProblemRating problemRating, - PracticeTest practiceTest) { + public ProblemForTest(String problemNumber, AnswerFormat answerFormat, String answer, int point, Long incorrectNum, + String conceptType, String unit, String subunit, double correctRate, + ProblemRating problemRating, + PracticeTest practiceTest) { this.problemNumber = problemNumber; this.answerFormat = answerFormat; this.answer = answer; @@ -66,11 +64,11 @@ public Problem(String problemNumber, AnswerFormat answerFormat, String answer, i } - public void addImage(ProblemImage image) { + public void addImage(ProblemImageForTest image) { this.image = image; } - public void calculateProblemRating(){ + public void calculateProblemRating() { this.problemRating = ProblemRating.findProblemRating(this); } @@ -82,7 +80,7 @@ public void updatePoint(int point) { this.point = point; } - public void updateCorrectRate (double correctRate) { + public void updateCorrectRate(double correctRate) { this.correctRate = correctRate; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/ProblemImage.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/ProblemImageForTest.java similarity index 68% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/ProblemImage.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/ProblemImageForTest.java index 63ec063..be116c5 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/ProblemImage.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/ProblemImageForTest.java @@ -1,9 +1,7 @@ -package com.moplus.moplus_server.domain.practiceTest.domain; +package com.moplus.moplus_server.domain.v0.practiceTest.domain; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -14,11 +12,11 @@ @Entity @Getter @NoArgsConstructor -public class ProblemImage { +public class ProblemImageForTest { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "problem_image_id") + @Column(name = "problem_image_for_test_id") private Long id; private String fileName; @@ -28,7 +26,7 @@ public class ProblemImage { private Long problemId; @Builder - public ProblemImage(String fileName, String imageUrl, Long problemId) { + public ProblemImageForTest(String fileName, String imageUrl, Long problemId) { this.fileName = fileName; this.imageUrl = imageUrl; this.problemId = problemId; diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/ProblemRating.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/ProblemRating.java similarity index 53% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/ProblemRating.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/ProblemRating.java index 742bc55..eece709 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/ProblemRating.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/ProblemRating.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.practiceTest.domain; +package com.moplus.moplus_server.domain.v0.practiceTest.domain; import java.util.Arrays; import lombok.AllArgsConstructor; @@ -8,12 +8,12 @@ @AllArgsConstructor public enum ProblemRating { - EXTREME("극상위권",0, 0, 30, "최상"), - TIER_1("1등급",1, 30, 50, "상"), - TIER_2("2등급", 2,50, 60, "중상"), - TIER_3("3등급", 3,60, 80, "중"), - TIER_4("4등급", 4,80, 90, "중하"), - OTHER("5등급 이하", 5,90, 100, "하"), + EXTREME("극상위권", 0, 0, 30, "최상"), + TIER_1("1등급", 1, 30, 50, "상"), + TIER_2("2등급", 2, 50, 60, "중상"), + TIER_3("3등급", 3, 60, 80, "중"), + TIER_4("4등급", 4, 80, 90, "중하"), + OTHER("5등급 이하", 5, 90, 100, "하"), ; private String rating; @@ -22,10 +22,10 @@ public enum ProblemRating { private double endCorrectRateRange; private String difficultyLevel; - public static ProblemRating findProblemRating(Problem problem) { + public static ProblemRating findProblemRating(ProblemForTest problemForTest) { return Arrays.stream(values()) - .filter(problemRating -> problemRating.startCorrectRateRange <= problem.getCorrectRate() - && problemRating.endCorrectRateRange > problem.getCorrectRate()) + .filter(problemRating -> problemRating.startCorrectRateRange <= problemForTest.getCorrectRate() + && problemRating.endCorrectRateRange > problemForTest.getCorrectRate()) .findFirst() .orElse(OTHER); } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/RatingRow.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/RatingRow.java similarity index 93% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/RatingRow.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/RatingRow.java index 67b1550..99f25ec 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/RatingRow.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/RatingRow.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.practiceTest.domain; +package com.moplus.moplus_server.domain.v0.practiceTest.domain; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @@ -15,7 +15,7 @@ public class RatingRow { private Integer standardScores; private Integer percentiles; - public RatingRow(int index){ + public RatingRow(int index) { this.rating = index; } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/RatingTable.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/RatingTable.java similarity index 88% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/RatingTable.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/RatingTable.java index 9801095..611885e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/RatingTable.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/RatingTable.java @@ -1,7 +1,7 @@ -package com.moplus.moplus_server.domain.practiceTest.domain; +package com.moplus.moplus_server.domain.v0.practiceTest.domain; -import com.moplus.moplus_server.domain.practiceTest.dto.admin.request.RatingTableRequest; -import com.moplus.moplus_server.domain.practiceTest.repository.converter.RatingRowConverter; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.RatingTableRequest; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.converter.RatingRowConverter; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Convert; diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/Subject.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/Subject.java similarity index 70% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/Subject.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/Subject.java index 531de9e..5b460b4 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/domain/Subject.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/Subject.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.practiceTest.domain; +package com.moplus.moplus_server.domain.v0.practiceTest.domain; import java.util.Arrays; @@ -9,13 +9,13 @@ @AllArgsConstructor public enum Subject { -// 화법과작문("화법과작문", 45, 100), + // 화법과작문("화법과작문", 45, 100), // 언어와매체("언어와매체", 45, 100), - 고1("고1",30,100), - 고2("고2",30,100), + 고1("고1", 30, 100), + 고2("고2", 30, 100), 미적분("미적분", 30, 100), - 확률과통계("확률과통계",30, 100), - 기하("기하",30, 100); + 확률과통계("확률과통계", 30, 100), + 기하("기하", 30, 100); // 영어("영어",45, 100), // 물리I("물리I",20, 50), // 화학I("화학I",20, 50), @@ -40,8 +40,8 @@ public enum Subject { public static Subject fromValue(String value) { return Arrays.stream(Subject.values()) - .filter(subject -> subject.value.equals(value)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("해당 값에 맞는 Subject가 없습니다: " + value)); + .filter(subject -> subject.value.equals(value)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("해당 값에 맞는 Subject가 없습니다: " + value)); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/PracticeTestRequest.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/PracticeTestRequest.java similarity index 79% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/PracticeTestRequest.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/PracticeTestRequest.java index c8a392f..b2c4a5e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/PracticeTestRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/PracticeTestRequest.java @@ -1,9 +1,7 @@ -package com.moplus.moplus_server.domain.practiceTest.dto.admin.request; +package com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingRow; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingTable; -import com.moplus.moplus_server.domain.practiceTest.domain.Subject; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.Subject; import java.util.ArrayList; import java.util.List; import lombok.Getter; @@ -20,7 +18,7 @@ public class PracticeTestRequest { private String provider; private String publicationYear; private String subject; - private List ratingTables = new ArrayList<>(); + private List ratingTables = new ArrayList<>(); public PracticeTestRequest(Long id, String name, String round, String provider, String publicationYear, String subject, List ratingTables) { @@ -33,7 +31,8 @@ public PracticeTestRequest(Long id, String name, String round, String provider, this.ratingTables = ratingTables; } - public static PracticeTestRequest getUpdateModelObject(PracticeTest practiceTest, List ratingTables) { + public static PracticeTestRequest getUpdateModelObject(PracticeTest practiceTest, + List ratingTables) { return new PracticeTestRequest( practiceTest.getId(), practiceTest.getName(), practiceTest.getRound(), practiceTest.getProvider(), practiceTest.getPublicationYear(), @@ -52,7 +51,7 @@ public static PracticeTestRequest getUpdateModelObject(PracticeTest practiceTest } public static PracticeTestRequest getCreateModelObject() { - return new PracticeTestRequest(null, "", "", "", "", null,RatingTableRequest.getDefaultRatingTableRequest()); + return new PracticeTestRequest(null, "", "", "", "", null, RatingTableRequest.getDefaultRatingTableRequest()); } public PracticeTest toEntity() { diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemCreateRequest.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemCreateRequest.java similarity index 54% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemCreateRequest.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemCreateRequest.java index 8cb51a5..7c6ff3e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/ProblemCreateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemCreateRequest.java @@ -1,8 +1,8 @@ -package com.moplus.moplus_server.domain.practiceTest.dto.admin.request; +package com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request; -import com.moplus.moplus_server.domain.practiceTest.domain.AnswerFormat; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.AnswerFormat; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; public record ProblemCreateRequest( String problemNumber, @@ -12,8 +12,8 @@ public record ProblemCreateRequest( double correctRate ) { - public Problem toEntity(PracticeTest practiceTest) { - return Problem.builder() + public ProblemForTest toEntity(PracticeTest practiceTest) { + return ProblemForTest.builder() .problemNumber(this.problemNumber()) .answerFormat(AnswerFormat.fromValue(this.answerFormat)) .answer(this.answer) diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemImageRequest.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemImageRequest.java new file mode 100644 index 0000000..dba2bd4 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemImageRequest.java @@ -0,0 +1,20 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request; + +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; +import lombok.Builder; + +@Builder +public record ProblemImageRequest( + Long problemId, + String problemNumber, + String imageUrl +) { + + public static ProblemImageRequest of(ProblemForTest problemForTest) { + return ProblemImageRequest.builder() + .problemId(problemForTest.getId()) + .problemNumber(problemForTest.getProblemNumber()) + .imageUrl(problemForTest.getImage() != null ? problemForTest.getImage().getImageUrl() : null) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemPostRequest.java new file mode 100644 index 0000000..631b814 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/ProblemPostRequest.java @@ -0,0 +1,7 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request; + +public record ProblemPostRequest( + +) { + +} diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/RatingTableRequest.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/RatingTableRequest.java similarity index 85% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/RatingTableRequest.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/RatingTableRequest.java index a144f36..69bcaea 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/dto/admin/request/RatingTableRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/RatingTableRequest.java @@ -1,7 +1,7 @@ -package com.moplus.moplus_server.domain.practiceTest.dto.admin.request; +package com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingRow; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingTable; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingRow; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingTable; import java.util.List; import lombok.Builder; import lombok.Getter; @@ -27,7 +27,8 @@ public RatingTableRequest(Long id, Long practiceId, String ratingProvider, List< public RatingTableRequest(String ratingProvider) { this.ratingProvider = ratingProvider; - this.ratingRows = List.of(new RatingRow(1), new RatingRow(2), new RatingRow(3), new RatingRow(4), new RatingRow(5), + this.ratingRows = List.of(new RatingRow(1), new RatingRow(2), new RatingRow(3), new RatingRow(4), + new RatingRow(5), new RatingRow(6), new RatingRow(7), new RatingRow(8), new RatingRow(9)); } diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/response/PracticeTestAdminResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/response/PracticeTestAdminResponse.java new file mode 100644 index 0000000..4cda784 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/response/PracticeTestAdminResponse.java @@ -0,0 +1,23 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.response; + +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import lombok.Builder; + +@Builder +public record PracticeTestAdminResponse( + Long id, + String name, + String round, + String provider, + String subject +) { + public static PracticeTestAdminResponse from(PracticeTest practiceTest) { + return PracticeTestAdminResponse.builder() + .id(practiceTest.getId()) + .name(practiceTest.getName()) + .provider(practiceTest.getProvider()) + .round(practiceTest.getRound()) + .subject(practiceTest.getSubject().getValue()) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/client/response/PracticeTestGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/client/response/PracticeTestGetResponse.java new file mode 100644 index 0000000..9a0b53a --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/client/response/PracticeTestGetResponse.java @@ -0,0 +1,27 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.dto.client.response; + +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import lombok.Builder; + +@Builder +public record PracticeTestGetResponse( + Long id, + String name, + String round, + String provider, + String subject, + long viewCount, + int totalSolvesCount +) { + public static PracticeTestGetResponse from(PracticeTest practiceTest) { + return PracticeTestGetResponse.builder() + .id(practiceTest.getId()) + .name(practiceTest.getName()) + .provider(practiceTest.getProvider()) + .round(practiceTest.getRound()) + .subject(practiceTest.getSubject().getValue()) + .viewCount(practiceTest.getViewCount()) + .totalSolvesCount(practiceTest.getSolvesCount()) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/client/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/client/response/ProblemGetResponse.java new file mode 100644 index 0000000..5fbea32 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/client/response/ProblemGetResponse.java @@ -0,0 +1,26 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.dto.client.response; + +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; +import lombok.Builder; + +@Builder +public record ProblemGetResponse( + Long id, + String problemNumber, + String answerFormat, + String answer, + int point, + double correctRate +) { + + public static ProblemGetResponse from(ProblemForTest problemForTest) { + return ProblemGetResponse.builder() + .id(problemForTest.getId()) + .answer(problemForTest.getAnswer()) + .problemNumber(problemForTest.getProblemNumber()) + .answerFormat(problemForTest.getAnswerFormat().getValue()) + .point(problemForTest.getPoint()) + .correctRate(problemForTest.getCorrectRate()) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/PracticeTestRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java similarity index 84% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/PracticeTestRepository.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java index c621581..948dc4e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/PracticeTestRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java @@ -1,6 +1,6 @@ -package com.moplus.moplus_server.domain.practiceTest.repository; +package com.moplus.moplus_server.domain.v0.practiceTest.repository; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; import jakarta.persistence.LockModeType; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemImageRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemImageRepository.java new file mode 100644 index 0000000..2034023 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemImageRepository.java @@ -0,0 +1,10 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.repository; + +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemImageForTest; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProblemImageRepository extends JpaRepository { + + Optional findByProblemId(Long problemId); +} diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemRepository.java new file mode 100644 index 0000000..43641ef --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemRepository.java @@ -0,0 +1,24 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.repository; + +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; +import jakarta.persistence.LockModeType; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ProblemRepository extends JpaRepository { + + List findAllByPracticeTestId(Long id); + + void deleteAllByPracticeTestId(Long id); + + Optional findByProblemNumberAndPracticeTestId(String problemNumber, Long practiceTest_id); + + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("SELECT p FROM ProblemForTest p WHERE p.problemNumber = :problem_number AND p.practiceTest.id = :practice_test_id") + Optional findByProblemNumberAndPracticeTestIdWithPessimisticLock( + @Param("problem_number") String problemNumber, @Param("practice_test_id") Long practiceTest_id); +} diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/RatingTableRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/RatingTableRepository.java similarity index 67% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/RatingTableRepository.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/RatingTableRepository.java index 4cc9544..023368b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/RatingTableRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/RatingTableRepository.java @@ -1,10 +1,11 @@ -package com.moplus.moplus_server.domain.practiceTest.repository; +package com.moplus.moplus_server.domain.v0.practiceTest.repository; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingTable; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingTable; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface RatingTableRepository extends JpaRepository { List findAllByPracticeTestId(Long practiceTestId); + void deleteAllByPracticeTestId(Long practiceTestId); } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/converter/RatingRowConverter.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/converter/RatingRowConverter.java similarity index 79% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/converter/RatingRowConverter.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/converter/RatingRowConverter.java index 98fdd5d..8967e95 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/repository/converter/RatingRowConverter.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/converter/RatingRowConverter.java @@ -1,8 +1,8 @@ -package com.moplus.moplus_server.domain.practiceTest.repository.converter; +package com.moplus.moplus_server.domain.v0.practiceTest.repository.converter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingRow; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingRow; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; @@ -34,7 +34,8 @@ public List convertToEntityAttribute(String dbData) { } try { - return objectMapper.readValue(dbData, objectMapper.getTypeFactory().constructCollectionType(List.class, RatingRow.class)); + return objectMapper.readValue(dbData, + objectMapper.getTypeFactory().constructCollectionType(List.class, RatingRow.class)); } catch (JsonProcessingException e) { throw new RuntimeException("JSON 문자열을 RatingRow 목록으로 변환할 수 없습니다 : ", e); } diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/PracticeTestAdminService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java similarity index 83% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/PracticeTestAdminService.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java index 126c227..4260692 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/PracticeTestAdminService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java @@ -1,13 +1,13 @@ -package com.moplus.moplus_server.domain.practiceTest.service.admin; - -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingTable; -import com.moplus.moplus_server.domain.practiceTest.domain.Subject; -import com.moplus.moplus_server.domain.practiceTest.dto.admin.request.PracticeTestRequest; -import com.moplus.moplus_server.domain.practiceTest.dto.admin.request.RatingTableRequest; -import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.practiceTest.repository.ProblemRepository; -import com.moplus.moplus_server.domain.practiceTest.repository.RatingTableRepository; +package com.moplus.moplus_server.domain.v0.practiceTest.service.admin; + +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingTable; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.Subject; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.PracticeTestRequest; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.RatingTableRequest; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.RatingTableRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import jakarta.transaction.Transactional; @@ -25,6 +25,22 @@ public class PracticeTestAdminService { private final ProblemRepository problemRepository; private final RatingTableAdminService ratingTableAdminService; + private static void addToPracticeTestUpdateModel(Model model, List ratingTables, + PracticeTest practiceTest) { + if (!ratingTables.isEmpty()) { + List ratingTableRequests = ratingTables.stream() + .map(RatingTableRequest::getRatingTableRequest) + .toList(); + model.addAttribute("practiceTestRequest", + PracticeTestRequest.getUpdateModelObject(practiceTest, ratingTableRequests)); + } else { + model.addAttribute("practiceTestRequest", PracticeTestRequest.getUpdateModelObject(practiceTest)); + } + } + + private static boolean isMathPracticeTest(PracticeTest practiceTest) { + return List.of("미적분", "확률과통계", "기하", "고1", "고2").contains(practiceTest.getSubject().getValue()); + } public PracticeTest getPracticeTestById(Long id) { return practiceTestRepository.findById(id) @@ -44,18 +60,6 @@ public void getPracticeTestUpdateModel(Model model, Long id) { model.addAttribute("subjects", Subject.values()); } - private static void addToPracticeTestUpdateModel(Model model, List ratingTables, PracticeTest practiceTest) { - if (!ratingTables.isEmpty()) { - List ratingTableRequests = ratingTables.stream() - .map(RatingTableRequest::getRatingTableRequest) - .toList(); - model.addAttribute("practiceTestRequest", - PracticeTestRequest.getUpdateModelObject(practiceTest, ratingTableRequests)); - } else { - model.addAttribute("practiceTestRequest", PracticeTestRequest.getUpdateModelObject(practiceTest)); - } - } - @Transactional public void deletePracticeTest(Long id) { ratingTableRepository.deleteAllByPracticeTestId(id); @@ -82,10 +86,6 @@ public void getProblemCreateModel(Model model, Long practiceTestId) { model.addAttribute("subject", practiceTest.getSubject().getValue()); } - private static boolean isMathPracticeTest(PracticeTest practiceTest) { - return List.of("미적분", "확률과통계", "기하", "고1", "고2").contains(practiceTest.getSubject().getValue()); - } - @Transactional public void updatePracticeTest(Long practiceTestId, PracticeTestRequest request) { PracticeTest practiceTest = getPracticeTestById(practiceTestId); diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/ProblemImageUploadService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java similarity index 77% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/ProblemImageUploadService.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java index 144a313..affbdff 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/ProblemImageUploadService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java @@ -1,17 +1,15 @@ -package com.moplus.moplus_server.domain.practiceTest.service.admin; +package com.moplus.moplus_server.domain.v0.practiceTest.service.admin; -import com.amazonaws.services.s3.model.PutObjectRequest; -import com.moplus.moplus_server.domain.practiceTest.domain.FileExtension; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; -import com.moplus.moplus_server.domain.practiceTest.domain.ProblemImage; -import com.moplus.moplus_server.domain.practiceTest.dto.admin.request.ProblemImageRequest; -import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.practiceTest.repository.ProblemImageRepository; -import com.moplus.moplus_server.domain.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.FileExtension; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemImageForTest; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.ProblemImageRequest; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemImageRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; -import com.moplus.moplus_server.global.utils.UUIDGenerator; import com.moplus.moplus_server.global.utils.s3.S3Util; import java.io.File; import java.io.IOException; @@ -43,18 +41,18 @@ public void setProblemImagesByPracticeTestId(Long practiceTestId, Model model) { public void uploadImage(Long practiceId, Long problemId, MultipartFile image) { PracticeTest practiceTest = practiceTestRepository.findById(practiceId) .orElseThrow(() -> new NotFoundException(ErrorCode.PRACTICE_TEST_NOT_FOUND)); - Problem problem = problemRepository.findById(problemId) + ProblemForTest problemForTest = problemRepository.findById(problemId) .orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); String fileName = uploadFile(image, problemId, practiceTest.getName()); String s3ObjectUrl = s3Util.getS3ObjectUrl(fileName); - ProblemImage problemImage = ProblemImage.builder() + ProblemImageForTest problemImageForTest = ProblemImageForTest.builder() .fileName(fileName) .problemId(problemId) .imageUrl(s3ObjectUrl) .build(); - ProblemImage saved = problemImageRepository.save(problemImage); - problem.addImage(saved); - problemRepository.save(problem); + ProblemImageForTest saved = problemImageRepository.save(problemImageForTest); + problemForTest.addImage(saved); + problemRepository.save(problemForTest); } public String uploadFile(MultipartFile file, Long problemId, String practiceTestName) { diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/RatingTableAdminService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/RatingTableAdminService.java similarity index 75% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/RatingTableAdminService.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/RatingTableAdminService.java index 7e2ad06..a88259a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/admin/RatingTableAdminService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/RatingTableAdminService.java @@ -1,8 +1,8 @@ -package com.moplus.moplus_server.domain.practiceTest.service.admin; +package com.moplus.moplus_server.domain.v0.practiceTest.service.admin; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingTable; -import com.moplus.moplus_server.domain.practiceTest.dto.admin.request.RatingTableRequest; -import com.moplus.moplus_server.domain.practiceTest.repository.RatingTableRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingTable; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.RatingTableRequest; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.RatingTableRepository; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -24,9 +24,9 @@ public void createRatingTable(RatingTableRequest request, Long practiceId) { public void updateOrSaveRatingTable(Long practiceTestId, List request) { List ratingTables = ratingTableRepository.findAllByPracticeTestId(practiceTestId); - if (ratingTables.isEmpty()) + if (ratingTables.isEmpty()) { request.forEach(ratingTableRequest -> createRatingTable(ratingTableRequest, practiceTestId)); - else{ + } else { for (int i = 0; i < ratingTables.size(); i++) { RatingTable ratingTable = ratingTables.get(i); ratingTable.updateByRatingTableRequest(request.get(i)); diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/client/OptimisticLockPracticeTestFacade.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/OptimisticLockPracticeTestFacade.java similarity index 88% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/service/client/OptimisticLockPracticeTestFacade.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/OptimisticLockPracticeTestFacade.java index afdb499..9627a7a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/client/OptimisticLockPracticeTestFacade.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/OptimisticLockPracticeTestFacade.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.practiceTest.service.client; +package com.moplus.moplus_server.domain.v0.practiceTest.service.client; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/PracticeTestService.java similarity index 64% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestService.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/PracticeTestService.java index 8935277..8a3a9a6 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/PracticeTestService.java @@ -1,11 +1,8 @@ -package com.moplus.moplus_server.domain.practiceTest.service.client; - -import com.moplus.moplus_server.domain.practiceTest.dto.admin.request.PracticeTestRequest; -import com.moplus.moplus_server.domain.practiceTest.dto.admin.response.PracticeTestAdminResponse; -import com.moplus.moplus_server.domain.practiceTest.dto.client.response.PracticeTestGetResponse; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.Subject; -import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; +package com.moplus.moplus_server.domain.v0.practiceTest.service.client; + +import com.moplus.moplus_server.domain.v0.practiceTest.dto.client.response.PracticeTestGetResponse; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.util.List; @@ -22,10 +19,10 @@ public class PracticeTestService { @Transactional(readOnly = true) @Cacheable("allPracticeTests") - public List getAllPracticeTest(){ + public List getAllPracticeTest() { return practiceTestRepository.findAllByOrderByViewCountDesc().stream() - .map(PracticeTestGetResponse::from) - .toList(); + .map(PracticeTestGetResponse::from) + .toList(); } @Transactional @@ -44,7 +41,7 @@ public void updateSolveCount(Long id) { @Transactional(readOnly = true) public PracticeTest getPracticeTestById(Long id) { return practiceTestRepository.findById(id) - .orElseThrow(() -> new NotFoundException(ErrorCode.PRACTICE_TEST_NOT_FOUND)); + .orElseThrow(() -> new NotFoundException(ErrorCode.PRACTICE_TEST_NOT_FOUND)); } @Transactional(readOnly = true) diff --git a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java similarity index 57% rename from src/main/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemService.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java index 42377e0..b22ff8b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/practiceTest/service/client/ProblemService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java @@ -1,10 +1,10 @@ -package com.moplus.moplus_server.domain.practiceTest.service.client; +package com.moplus.moplus_server.domain.v0.practiceTest.service.client; -import com.moplus.moplus_server.domain.practiceTest.dto.admin.request.ProblemCreateRequest; -import com.moplus.moplus_server.domain.practiceTest.dto.client.response.ProblemGetResponse; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.Problem; -import com.moplus.moplus_server.domain.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.ProblemCreateRequest; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.client.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import jakarta.servlet.http.HttpServletRequest; @@ -36,43 +36,44 @@ public void saveProblems(PracticeTest practiceTest, HttpServletRequest request) correctRate); problems.add(problem); } - List problemsEntities = problems.stream() + List problemsEntities = problems.stream() .map(problem -> problem.toEntity(practiceTest)) .toList(); problemsEntities - .forEach(Problem::calculateProblemRating); + .forEach(ProblemForTest::calculateProblemRating); problemRepository.saveAll(problemsEntities); } @Transactional public void updateProblems(PracticeTest practiceTest, HttpServletRequest request) { - List problems = problemRepository.findAllByPracticeTestId(practiceTest.getId()); + List problemForTests = problemRepository.findAllByPracticeTestId(practiceTest.getId()); for (int i = 1; i <= practiceTest.getSubject().getProblemCount(); i++) { - Problem problem = problems.get(i - 1); - problem.updateAnswer(request.getParameter("answer_" + i)); - problem.updatePoint(Integer.parseInt(request.getParameter("point_" + i))); - problem.updateCorrectRate(Double.parseDouble(request.getParameter("correctRate_" + i))); + ProblemForTest problemForTest = problemForTests.get(i - 1); + problemForTest.updateAnswer(request.getParameter("answer_" + i)); + problemForTest.updatePoint(Integer.parseInt(request.getParameter("point_" + i))); + problemForTest.updateCorrectRate(Double.parseDouble(request.getParameter("correctRate_" + i))); - problem.calculateProblemRating(); - problemRepository.save(problem); + problemForTest.calculateProblemRating(); + problemRepository.save(problemForTest); } } - public Problem getProblemByPracticeTestIdAndNumber(Long practiceId, String problemNumber) { + public ProblemForTest getProblemByPracticeTestIdAndNumber(Long practiceId, String problemNumber) { return problemRepository.findByProblemNumberAndPracticeTestId(problemNumber, practiceId) .orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); } @Transactional - public Problem updateCorrectRate(Long practiceTestId, String problemNumber, double correctRate) { - Problem problem = problemRepository.findByProblemNumberAndPracticeTestIdWithPessimisticLock(problemNumber, + public ProblemForTest updateCorrectRate(Long practiceTestId, String problemNumber, double correctRate) { + ProblemForTest problemForTest = problemRepository.findByProblemNumberAndPracticeTestIdWithPessimisticLock( + problemNumber, practiceTestId) .orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); - problem.getPracticeTest(); - problem.updateCorrectRate(correctRate); - return problemRepository.save(problem); + problemForTest.getPracticeTest(); + problemForTest.updateCorrectRate(correctRate); + return problemRepository.save(problemForTest); } public List getProblemsByTestId(Long testId) { diff --git a/src/main/java/com/moplus/moplus_server/global/scheduler/TestResultScheduler.java b/src/main/java/com/moplus/moplus_server/global/scheduler/TestResultScheduler.java index 3168c59..4a453ae 100644 --- a/src/main/java/com/moplus/moplus_server/global/scheduler/TestResultScheduler.java +++ b/src/main/java/com/moplus/moplus_server/global/scheduler/TestResultScheduler.java @@ -1,9 +1,9 @@ package com.moplus.moplus_server.global.scheduler; -import com.moplus.moplus_server.domain.TestResult.entity.TestResult; -import com.moplus.moplus_server.domain.TestResult.repository.TestResultRepository; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; +import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; +import com.moplus.moplus_server.domain.v0.TestResult.repository.TestResultRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; import java.time.Duration; import java.util.List; import lombok.RequiredArgsConstructor; @@ -30,7 +30,7 @@ public void calculateAverageSolvingTime() { Duration sum = Duration.ZERO; List allByPracticeTestId = - testResultRepository.findAllByPracticeTestId(practiceTest.getId()); + testResultRepository.findAllByPracticeTestId(practiceTest.getId()); long validCount = 0; @@ -52,7 +52,8 @@ public void calculateAverageSolvingTime() { practiceTest.updateAverageSolvingTime(average); practiceTestRepository.save(practiceTest); } - System.out.println("평균 풀이 시간 계산 완료 : " + practiceTest.getId() + "L, 평균 시간 " + practiceTest.getAverageSolvingTime()); + System.out.println( + "평균 풀이 시간 계산 완료 : " + practiceTest.getId() + "L, 평균 시간 " + practiceTest.getAverageSolvingTime()); } } } diff --git a/src/main/java/com/moplus/moplus_server/global/utils/s3/S3Util.java b/src/main/java/com/moplus/moplus_server/global/utils/s3/S3Util.java index 5e94fb3..d2abde8 100644 --- a/src/main/java/com/moplus/moplus_server/global/utils/s3/S3Util.java +++ b/src/main/java/com/moplus/moplus_server/global/utils/s3/S3Util.java @@ -1,22 +1,13 @@ package com.moplus.moplus_server.global.utils.s3; -import com.amazonaws.HttpMethod; import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.model.DeleteObjectRequest; -import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import com.amazonaws.services.s3.model.PutObjectRequest; -import com.moplus.moplus_server.domain.practiceTest.domain.FileExtension; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; -import com.moplus.moplus_server.global.utils.UUIDGenerator; import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; @Component @RequiredArgsConstructor diff --git a/src/main/resources/templates/answerInputForm.html b/src/main/resources/templates/answerInputForm.html index c7656fa..bf73a3f 100644 --- a/src/main/resources/templates/answerInputForm.html +++ b/src/main/resources/templates/answerInputForm.html @@ -1,165 +1,168 @@ - - - 답안 입력 - - + if (totalPoints !== 100) { + alert('배점의 합이 100점이 되어야 합니다.'); + return false; // 폼 제출 중지 + } + return true; // 폼 제출 허용 + } +
-

답안 입력

-
- - - - - - - - - - - - - - + + +
번호배점 선택선지 선택정답률 입력
- - - - - - - - - +

답안 입력

+ + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - + + + + + + + + - - + + - - - - -
번호배점 선택선지 선택정답률 입력
+ + + + + + + + + - - - - - - - - - - -
- - - - - - - - - - - -
-
- - -
-
-
- - - - - - - - - - - -
-
- - -
-
+
+ + + + + + + + + + + +
+
+ + +
+
+
+ + + + + + + + + + + +
+
+ + +
+
- - - - - - - - - - - + + + + + + + + + + + - -
- - + +
+ +
+ +
diff --git a/src/main/resources/templates/imageUploadPage.html b/src/main/resources/templates/imageUploadPage.html index a6593ac..b44b58c 100644 --- a/src/main/resources/templates/imageUploadPage.html +++ b/src/main/resources/templates/imageUploadPage.html @@ -2,9 +2,9 @@ - + 문제 이미지 업로드 - +
@@ -20,22 +20,24 @@

문제 이미지 업로드

- + - 문제 ID - 문제 번호 + 문제 ID + 문제 번호 - 문제 이미지 + 문제 이미지 - 이미지 없음 + 이미지 없음 -
- - - + + + +
@@ -45,7 +47,7 @@

문제 이미지 업로드

- +
diff --git a/src/test/java/com/moplus/moplus_server/domain/TestResult/entity/EstimatedRatingTest.java b/src/test/java/com/moplus/moplus_server/domain/TestResult/entity/EstimatedRatingTest.java index 6855ec4..205d3c8 100644 --- a/src/test/java/com/moplus/moplus_server/domain/TestResult/entity/EstimatedRatingTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/TestResult/entity/EstimatedRatingTest.java @@ -2,11 +2,11 @@ import static org.junit.jupiter.api.Assertions.*; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingRow; -import com.moplus.moplus_server.domain.practiceTest.domain.RatingTable; +import com.moplus.moplus_server.domain.v0.TestResult.entity.EstimatedRating; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingRow; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingTable; import java.util.ArrayList; import java.util.List; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; diff --git a/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java b/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java index 9f0defd..2ce90a3 100644 --- a/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java @@ -32,7 +32,7 @@ class MemberControllerTest { @Nested - class 어드민_로그인 { + class 내정보_가져오기 { @Autowired private JwtProperties jwtProperties; diff --git a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java index 6558a15..a39a9cd 100644 --- a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java @@ -2,9 +2,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.service.client.PracticeTestService; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; diff --git a/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java b/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java index e6b8286..bd942a5 100644 --- a/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java +++ b/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java @@ -1,10 +1,10 @@ package com.moplus.moplus_server.global.scheduler; -import com.moplus.moplus_server.domain.TestResult.entity.TestResult; -import com.moplus.moplus_server.domain.TestResult.repository.TestResultRepository; -import com.moplus.moplus_server.domain.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.practiceTest.domain.Subject; -import com.moplus.moplus_server.domain.practiceTest.repository.PracticeTestRepository; +import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; +import com.moplus.moplus_server.domain.v0.TestResult.repository.TestResultRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.domain.Subject; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; import java.lang.reflect.Field; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -36,32 +36,32 @@ class TestResultSchedulerTest { private TestResult testResult2; @BeforeEach - void setUp() throws NoSuchFieldException, IllegalAccessException{ + void setUp() throws NoSuchFieldException, IllegalAccessException { // PracticeTest 객체 초기화 practiceTest = PracticeTest.builder() - .name("Sample Test") - .round("1st Round") - .provider("Provider A") - .publicationYear("2024") - .subject(Subject.미적분) - .build(); + .name("Sample Test") + .round("1st Round") + .provider("Provider A") + .publicationYear("2024") + .subject(Subject.미적분) + .build(); Field idField = PracticeTest.class.getDeclaredField("id"); idField.setAccessible(true); // private 필드 접근 허용 idField.set(practiceTest, 1L); // // TestResult 객체 초기화 (각 테스트의 풀이 시간을 설정) testResult1 = TestResult.builder() - .score(85) - .solvingTime(Duration.ofMinutes(30)) // 30분 걸림 - .practiceTestId(practiceTest.getId()) - .build(); + .score(85) + .solvingTime(Duration.ofMinutes(30)) // 30분 걸림 + .practiceTestId(practiceTest.getId()) + .build(); practiceTest.plus1SolvesCount(); testResult2 = TestResult.builder() - .score(90) - .solvingTime(Duration.ofMinutes(45)) // 45분 걸림 - .practiceTestId(practiceTest.getId()) - .build(); + .score(90) + .solvingTime(Duration.ofMinutes(45)) // 45분 걸림 + .practiceTestId(practiceTest.getId()) + .build(); practiceTest.plus1SolvesCount(); } @@ -72,7 +72,6 @@ void setUp() throws NoSuchFieldException, IllegalAccessException{ List testResultsForPracticeTest1 = List.of(testResult1, testResult2); when(testResultRepository.findAllByPracticeTestId(1L)).thenReturn(testResultsForPracticeTest1); - // 메서드 실행 testResultScheduler.calculateAverageSolvingTime(); From e778a6982c587dea8d9a661cf980609dfeda5bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Mon, 27 Jan 2025 00:26:56 +0900 Subject: [PATCH 021/115] =?UTF-8?q?[feat/#21]=EB=AC=B8=ED=95=AD,=20?= =?UTF-8?q?=EC=83=88=EB=81=BC=EB=AC=B8=ED=95=AD,=20=EA=B0=9C=EB=85=90?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/concept/domain/ConceptTag.java | 23 +++++++++ .../domain/problem/domain/ChildProblem.java | 35 ++++++++++++++ .../domain/problem/domain/Problem.java | 47 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/ChildProblem.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java b/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java new file mode 100644 index 0000000..ff0bf21 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java @@ -0,0 +1,23 @@ +package com.moplus.moplus_server.domain.concept.domain; + +import com.moplus.moplus_server.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor +public class ConceptTag extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "concept_tag_id") + Long id; + + String name; +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/ChildProblem.java new file mode 100644 index 0000000..21291ef --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/ChildProblem.java @@ -0,0 +1,35 @@ +package com.moplus.moplus_server.domain.problem.domain; + +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import java.util.Set; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor +public class ChildProblem { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "problem_id") + Long id; + @ElementCollection + @CollectionTable(name = "child_problem_concept", joinColumns = @JoinColumn(name = "concept_tag_id")) + Set conceptTagIds; + private String problemImageUrl; + private String answer; + + public ChildProblem(String problemImageUrl, String answer, Set conceptTagIds) { + this.problemImageUrl = problemImageUrl; + this.answer = answer; + this.conceptTagIds = conceptTagIds; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java new file mode 100644 index 0000000..15b76fc --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java @@ -0,0 +1,47 @@ +package com.moplus.moplus_server.domain.problem.domain; + +import com.moplus.moplus_server.global.common.BaseEntity; +import jakarta.persistence.CascadeType; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor +public class Problem extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "problem_id") + Long id; + + String practiceTestId; + int number; + int answer; + String comment; + String mainProblemImageUrl; + String mainAnalysisImageUrl; + String readingTipImageUrl; + String seniorTipImageUrl; + String prescriptionImageUrl; + + @ElementCollection + @CollectionTable(name = "problem_concept", joinColumns = @JoinColumn(name = "concept_tag_id")) + Set conceptTagIds; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "problem_id") + private List childProblems = new ArrayList<>(); +} From 0bff27af1e47488b221cf7135578a7e73baa9f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Mon, 27 Jan 2025 23:32:30 +0900 Subject: [PATCH 022/115] =?UTF-8?q?[feat/#21]=20repository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/concept/domain/ConceptTag.java | 4 ++++ .../concept/repository/ConceptTagRepository.java | 7 +++++++ .../domain/problem/domain/Problem.java | 15 +++++++++++++++ .../repository/ChildProblemRepository.java | 7 +++++++ .../problem/repository/ProblemRepository.java | 7 +++++++ 5 files changed, 40 insertions(+) create mode 100644 src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java b/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java index ff0bf21..86e49c2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java +++ b/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java @@ -20,4 +20,8 @@ public class ConceptTag extends BaseEntity { Long id; String name; + + public ConceptTag(String name) { + this.name = name; + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java b/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java new file mode 100644 index 0000000..df5bc5c --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java @@ -0,0 +1,7 @@ +package com.moplus.moplus_server.domain.concept.repository; + +import com.moplus.moplus_server.domain.concept.domain.ConceptTag; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ConceptTagRepository extends JpaRepository { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java index 15b76fc..5a192fc 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java @@ -44,4 +44,19 @@ public class Problem extends BaseEntity { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "problem_id") private List childProblems = new ArrayList<>(); + + public Problem(String practiceTestId, int number, int answer, String comment, String mainProblemImageUrl, + String mainAnalysisImageUrl, String readingTipImageUrl, String seniorTipImageUrl, + String prescriptionImageUrl, Set conceptTagIds) { + this.practiceTestId = practiceTestId; + this.number = number; + this.answer = answer; + this.comment = comment; + this.mainProblemImageUrl = mainProblemImageUrl; + this.mainAnalysisImageUrl = mainAnalysisImageUrl; + this.readingTipImageUrl = readingTipImageUrl; + this.seniorTipImageUrl = seniorTipImageUrl; + this.prescriptionImageUrl = prescriptionImageUrl; + this.conceptTagIds = conceptTagIds; + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java new file mode 100644 index 0000000..a27fe22 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java @@ -0,0 +1,7 @@ +package com.moplus.moplus_server.domain.problem.repository; + +import com.moplus.moplus_server.domain.problem.domain.ChildProblem; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChildProblemRepository extends JpaRepository { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java new file mode 100644 index 0000000..84c1f1f --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java @@ -0,0 +1,7 @@ +package com.moplus.moplus_server.domain.problem.repository; + +import com.moplus.moplus_server.domain.problem.domain.Problem; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProblemRepository extends JpaRepository { +} From 76bb72bd43bc7ac96f1e0bcaa6116b6eed35debd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 28 Jan 2025 00:45:50 +0900 Subject: [PATCH 023/115] =?UTF-8?q?[feat/#21]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=9F=20=EC=8B=9C=20=EC=83=88=EB=81=BC=EB=AC=B8?= =?UTF-8?q?=ED=95=AD=20lazy=EB=A1=9C=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moplus/moplus_server/domain/problem/domain/Problem.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java index 5a192fc..fcaff94 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java @@ -6,6 +6,7 @@ import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -41,7 +42,7 @@ public class Problem extends BaseEntity { @CollectionTable(name = "problem_concept", joinColumns = @JoinColumn(name = "concept_tag_id")) Set conceptTagIds; - @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name = "problem_id") private List childProblems = new ArrayList<>(); From a416fbde0182f120fdf8213a9514281346420aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 28 Jan 2025 17:28:05 +0900 Subject: [PATCH 024/115] =?UTF-8?q?[fix/#17]=20jwtAuthenticationProvider?= =?UTF-8?q?=EC=99=80=20authenticationArgurentResolver=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AuthenticationArgumentResolver.java | 16 ++++++---------- .../global/error/exception/ErrorCode.java | 1 + 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/global/annotation/AuthenticationArgumentResolver.java b/src/main/java/com/moplus/moplus_server/global/annotation/AuthenticationArgumentResolver.java index 2b20910..3952759 100644 --- a/src/main/java/com/moplus/moplus_server/global/annotation/AuthenticationArgumentResolver.java +++ b/src/main/java/com/moplus/moplus_server/global/annotation/AuthenticationArgumentResolver.java @@ -4,7 +4,6 @@ import com.moplus.moplus_server.domain.member.repository.MemberRepository; import com.moplus.moplus_server.global.error.exception.BusinessException; import com.moplus.moplus_server.global.error.exception.ErrorCode; -import com.moplus.moplus_server.global.error.exception.NotFoundException; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; import org.springframework.security.core.Authentication; @@ -35,19 +34,16 @@ public Member resolveArgument(MethodParameter parameter, ModelAndViewContainer m } private Member getCurrentMember() { - return memberRepository - .findById(getCurrentMemberId()) - .orElseThrow(() -> new NotFoundException(ErrorCode.MEMBER_NOT_FOUND)); - } - - private Long getCurrentMemberId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - + if (authentication == null || !authentication.isAuthenticated()) { throw new BusinessException(ErrorCode.AUTH_NOT_FOUND); } + Object principal = authentication.getPrincipal(); - Member principal = (Member) authentication.getPrincipal(); - return principal.getId(); + if (!(principal instanceof Member)) { + throw new BusinessException(ErrorCode.INVALID_PRINCIPAL); + } + return (Member) principal; } } diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 31735e8..b8c892f 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -21,6 +21,7 @@ public enum ErrorCode { TOKEN_SUBJECT_FORMAT_ERROR(HttpStatus.UNAUTHORIZED, "Subject 값에 Long 타입이 아닌 다른 타입이 들어있습니다."), AT_EXPIRED_AND_RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AT는 만료되었고 RT는 비어있습니다."), RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "RT가 비어있습니다"), + INVALID_PRINCIPAL(HttpStatus.UNAUTHORIZED, "잘못된 Principal입니다"), //모의고사 PRACTICE_TEST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 모의고사를 찾을 수 없습니다"), From 92e865e4a814b45d45304d3a526dd82f37937390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:42:09 +0900 Subject: [PATCH 025/115] =?UTF-8?q?[fix/#24]=20problem=20repository=20bean?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EA=B2=B9=EC=B9=A8=20=EC=B6=A9=EB=8F=8C?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailResultApplicationService.java | 6 +++--- ...tory.java => ProblemForTestRepository.java} | 2 +- .../admin/PracticeTestAdminService.java | 6 +++--- .../admin/ProblemImageUploadService.java | 11 ++++++----- .../service/client/ProblemService.java | 18 +++++++++--------- .../client/PracticeTestServiceTest.java | 4 ++-- 6 files changed, 24 insertions(+), 23 deletions(-) rename src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/{ProblemRepository.java => ProblemForTestRepository.java} (92%) diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java index 085cfad..1382c1f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java @@ -14,7 +14,7 @@ import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.time.Duration; @@ -33,7 +33,7 @@ public class DetailResultApplicationService { private final EstimatedRatingRepository estimatedRatingRepository; private final IncorrectProblemService incorrectProblemService; private final IncorrectProblemRepository incorrectProblemRepository; - private final ProblemRepository problemRepository; + private final ProblemForTestRepository problemForTestRepository; @Transactional public void saveApplication(DetailResultApplicationPostRequest request) { @@ -57,7 +57,7 @@ public ReviewNoteGetResponse getReviewNoteInfo(Long testResultId) { List incorrectProblemForTests = incorrectProblemRepository.findAllByTestResultId(testResultId) .stream() .map(IncorrectProblem::getProblemId) - .map(problemId -> problemRepository.findById(problemId).orElseThrow()) + .map(problemId -> problemForTestRepository.findById(problemId).orElseThrow()) .toList(); List forCurrentRating = incorrectProblemForTests.stream() diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemForTestRepository.java similarity index 92% rename from src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemRepository.java rename to src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemForTestRepository.java index 43641ef..b45d893 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/ProblemForTestRepository.java @@ -9,7 +9,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface ProblemRepository extends JpaRepository { +public interface ProblemForTestRepository extends JpaRepository { List findAllByPracticeTestId(Long id); diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java index 4260692..00e7c6e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java @@ -6,7 +6,7 @@ import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.PracticeTestRequest; import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.RatingTableRequest; import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; import com.moplus.moplus_server.domain.v0.practiceTest.repository.RatingTableRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; @@ -22,7 +22,7 @@ public class PracticeTestAdminService { private final PracticeTestRepository practiceTestRepository; private final RatingTableRepository ratingTableRepository; - private final ProblemRepository problemRepository; + private final ProblemForTestRepository problemForTestRepository; private final RatingTableAdminService ratingTableAdminService; private static void addToPracticeTestUpdateModel(Model model, List ratingTables, @@ -63,7 +63,7 @@ public void getPracticeTestUpdateModel(Model model, Long id) { @Transactional public void deletePracticeTest(Long id) { ratingTableRepository.deleteAllByPracticeTestId(id); - problemRepository.deleteAllByPracticeTestId(id); + problemForTestRepository.deleteAllByPracticeTestId(id); practiceTestRepository.deleteById(id); } diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java index affbdff..411f1e1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java @@ -6,8 +6,8 @@ import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemImageForTest; import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.ProblemImageRequest; import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemImageRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import com.moplus.moplus_server.global.utils.s3.S3Util; @@ -26,12 +26,13 @@ public class ProblemImageUploadService { private final S3Util s3Util; private final PracticeTestRepository practiceTestRepository; - private final ProblemRepository problemRepository; + private final ProblemForTestRepository problemForTestRepository; private final ProblemImageRepository problemImageRepository; @Transactional(readOnly = true) public void setProblemImagesByPracticeTestId(Long practiceTestId, Model model) { - List imageRequests = problemRepository.findAllByPracticeTestId(practiceTestId).stream() + List imageRequests = problemForTestRepository.findAllByPracticeTestId(practiceTestId) + .stream() .map(ProblemImageRequest::of) .toList(); model.addAttribute("problemImageRequests", imageRequests); @@ -41,7 +42,7 @@ public void setProblemImagesByPracticeTestId(Long practiceTestId, Model model) { public void uploadImage(Long practiceId, Long problemId, MultipartFile image) { PracticeTest practiceTest = practiceTestRepository.findById(practiceId) .orElseThrow(() -> new NotFoundException(ErrorCode.PRACTICE_TEST_NOT_FOUND)); - ProblemForTest problemForTest = problemRepository.findById(problemId) + ProblemForTest problemForTest = problemForTestRepository.findById(problemId) .orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); String fileName = uploadFile(image, problemId, practiceTest.getName()); String s3ObjectUrl = s3Util.getS3ObjectUrl(fileName); @@ -52,7 +53,7 @@ public void uploadImage(Long practiceId, Long problemId, MultipartFile image) { .build(); ProblemImageForTest saved = problemImageRepository.save(problemImageForTest); problemForTest.addImage(saved); - problemRepository.save(problemForTest); + problemForTestRepository.save(problemForTest); } public String uploadFile(MultipartFile file, Long problemId, String practiceTestName) { diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java index b22ff8b..e5560a6 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java @@ -4,7 +4,7 @@ import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.ProblemCreateRequest; import com.moplus.moplus_server.domain.v0.practiceTest.dto.client.response.ProblemGetResponse; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import jakarta.servlet.http.HttpServletRequest; @@ -18,7 +18,7 @@ @RequiredArgsConstructor public class ProblemService { - private final ProblemRepository problemRepository; + private final ProblemForTestRepository problemForTestRepository; @Transactional public void saveProblems(PracticeTest practiceTest, HttpServletRequest request) { @@ -41,12 +41,12 @@ public void saveProblems(PracticeTest practiceTest, HttpServletRequest request) .toList(); problemsEntities .forEach(ProblemForTest::calculateProblemRating); - problemRepository.saveAll(problemsEntities); + problemForTestRepository.saveAll(problemsEntities); } @Transactional public void updateProblems(PracticeTest practiceTest, HttpServletRequest request) { - List problemForTests = problemRepository.findAllByPracticeTestId(practiceTest.getId()); + List problemForTests = problemForTestRepository.findAllByPracticeTestId(practiceTest.getId()); for (int i = 1; i <= practiceTest.getSubject().getProblemCount(); i++) { ProblemForTest problemForTest = problemForTests.get(i - 1); @@ -55,29 +55,29 @@ public void updateProblems(PracticeTest practiceTest, HttpServletRequest request problemForTest.updateCorrectRate(Double.parseDouble(request.getParameter("correctRate_" + i))); problemForTest.calculateProblemRating(); - problemRepository.save(problemForTest); + problemForTestRepository.save(problemForTest); } } public ProblemForTest getProblemByPracticeTestIdAndNumber(Long practiceId, String problemNumber) { - return problemRepository.findByProblemNumberAndPracticeTestId(problemNumber, practiceId) + return problemForTestRepository.findByProblemNumberAndPracticeTestId(problemNumber, practiceId) .orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); } @Transactional public ProblemForTest updateCorrectRate(Long practiceTestId, String problemNumber, double correctRate) { - ProblemForTest problemForTest = problemRepository.findByProblemNumberAndPracticeTestIdWithPessimisticLock( + ProblemForTest problemForTest = problemForTestRepository.findByProblemNumberAndPracticeTestIdWithPessimisticLock( problemNumber, practiceTestId) .orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); problemForTest.getPracticeTest(); problemForTest.updateCorrectRate(correctRate); - return problemRepository.save(problemForTest); + return problemForTestRepository.save(problemForTest); } public List getProblemsByTestId(Long testId) { - return problemRepository.findAllByPracticeTestId(testId).stream() + return problemForTestRepository.findAllByPracticeTestId(testId).stream() .map(ProblemGetResponse::from) .toList(); } diff --git a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java index a39a9cd..5a639ed 100644 --- a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java @@ -4,7 +4,7 @@ import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; import com.moplus.moplus_server.domain.v0.practiceTest.service.client.PracticeTestService; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -25,7 +25,7 @@ class PracticeTestServiceTest { @Autowired private PracticeTestRepository practiceTestRepository; @Autowired - private ProblemRepository problemRepository; + private ProblemForTestRepository problemForTestRepository; @BeforeEach void setup() { From 408ee592e07f9463934b7806532a9bb513e5ebb3 Mon Sep 17 00:00:00 2001 From: HongGit Date: Wed, 29 Jan 2025 17:45:21 +0900 Subject: [PATCH 026/115] =?UTF-8?q?[fix/#12]=20=EB=AA=A8=EB=8B=88=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=8B=9C=ED=81=90=EB=A6=AC=ED=8B=B0=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moplus_server/global/config/security/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java index 2ee9340..4582202 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java @@ -36,7 +36,7 @@ public class SecurityConfig { private final EmailPasswordSuccessHandler emailPasswordSuccessHandler; private final JwtUtil jwtUtil; - private String[] allowUrls = {"/", "/favicon.ico", "/swagger-ui/**", "/v3/**"}; + private String[] allowUrls = {"/", "/favicon.ico", "/swagger-ui/**", "/v3/**", "/actuator/**"}; @Value("${cors-allowed-origins}") private List corsAllowedOrigins; From 8f5d86541e5850e4eac08813355b67d4f5fa558e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:39:01 +0900 Subject: [PATCH 027/115] =?UTF-8?q?[feat/#23]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1,=20=EC=88=98=EC=A0=95,=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConceptTagController.java | 29 ++ .../dto/response/ConceptTagResponse.java | 15 + .../repository/ConceptTagRepository.java | 14 + .../controller/ProblemGetController.java | 27 ++ .../controller/ProblemSaveController.java | 39 ++ .../domain/problem/domain/Answer.java | 45 ++ .../domain/problem/domain/ChildProblem.java | 35 -- .../domain/problem/domain/Problem.java | 63 --- .../domain/childProblem/ChildProblem.java | 71 ++++ .../domain/practiceTest/PracticeTest.java | 34 ++ .../problem/domain/practiceTest/Subject.java | 30 ++ .../problem/domain/problem/Problem.java | 105 +++++ .../problem/domain/problem/ProblemId.java | 18 + .../domain/problem/ProblemIdService.java | 47 +++ .../problem/domain/problem/ProblemType.java | 37 ++ .../request/ChildProblemDeleteRequest.java | 6 + .../dto/request/ChildProblemPostRequest.java | 13 + .../request/ChildProblemUpdateRequest.java | 14 + .../dto/request/ProblemPostRequest.java | 37 ++ .../dto/request/ProblemUpdateRequest.java | 21 + .../dto/response/ChildProblemGetResponse.java | 26 ++ .../dto/response/ProblemGetResponse.java | 42 ++ .../repository/ChildProblemRepository.java | 2 +- .../repository/PracticeTestTagRepository.java | 14 + .../problem/repository/ProblemRepository.java | 20 +- .../problem/service/ProblemGetService.java | 22 + .../problem/service/ProblemSaveService.java | 57 +++ .../DetailResultApplicationService.java | 6 +- .../v0/practiceTest/domain/PracticeTest.java | 15 +- .../v0/practiceTest/domain/RatingTable.java | 1 + .../v0/practiceTest/domain/Subject.java | 47 --- .../admin/request/PracticeTestRequest.java | 8 +- .../repository/PracticeTestRepository.java | 4 - ...ory.java => ProblemForTestRepository.java} | 2 +- .../admin/PracticeTestAdminService.java | 8 +- .../admin/ProblemImageUploadService.java | 11 +- .../service/client/PracticeTestService.java | 2 +- .../service/client/ProblemService.java | 18 +- .../exception/AlreadyExistException.java | 7 + .../global/error/exception/ErrorCode.java | 9 +- .../resources/templates/answerInputForm.html | 91 ++-- .../resources/templates/imageUploadPage.html | 8 +- .../resources/templates/practiceTestList.html | 106 ++--- .../resources/templates/testInputForm.html | 391 +++++++++--------- .../client/PracticeTestServiceTest.java | 4 +- .../scheduler/TestResultSchedulerTest.java | 11 +- 46 files changed, 1158 insertions(+), 474 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/concept/dto/response/ConceptTagResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemGetController.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSaveController.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/ChildProblem.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemDeleteRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/repository/PracticeTestTagRepository.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java delete mode 100644 src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/Subject.java rename src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/{ProblemRepository.java => ProblemForTestRepository.java} (92%) create mode 100644 src/main/java/com/moplus/moplus_server/global/error/exception/AlreadyExistException.java diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java b/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java new file mode 100644 index 0000000..094e6ae --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java @@ -0,0 +1,29 @@ +package com.moplus.moplus_server.domain.concept.controller; + +import com.moplus.moplus_server.domain.concept.dto.response.ConceptTagResponse; +import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; +import io.swagger.v3.oas.annotations.Operation; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/conceptTags") +@RequiredArgsConstructor +public class ConceptTagController { + + ConceptTagRepository conceptTagRepository; + + @GetMapping("") + @Operation(summary = "모든 개념 태그 리스트 조회") + public ResponseEntity> getConceptTags( + ) { + List responses = conceptTagRepository.findAll().stream() + .map(ConceptTagResponse::of) + .toList(); + return ResponseEntity.ok(responses); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/dto/response/ConceptTagResponse.java b/src/main/java/com/moplus/moplus_server/domain/concept/dto/response/ConceptTagResponse.java new file mode 100644 index 0000000..e92b490 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/concept/dto/response/ConceptTagResponse.java @@ -0,0 +1,15 @@ +package com.moplus.moplus_server.domain.concept.dto.response; + +import com.moplus.moplus_server.domain.concept.domain.ConceptTag; + +public record ConceptTagResponse( + Long id, + String name +) { + public static ConceptTagResponse of(ConceptTag entity) { + return new ConceptTagResponse( + entity.getId(), + entity.getName() + ); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java b/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java index df5bc5c..682a2f3 100644 --- a/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java @@ -1,7 +1,21 @@ package com.moplus.moplus_server.domain.concept.repository; import com.moplus.moplus_server.domain.concept.domain.ConceptTag; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; +import java.util.List; +import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; public interface ConceptTagRepository extends JpaRepository { + + default void existsByIdElseThrow(Set ids) { + List foundIds = findAllById(ids).stream() + .map(ConceptTag::getId) // 엔티티의 ID 추출 + .toList(); + + if (ids.size() != foundIds.size()) { + throw new NotFoundException(ErrorCode.CONCEPT_TAG_NOT_FOUND_IN_LIST); + } + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemGetController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemGetController.java new file mode 100644 index 0000000..bf465bf --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemGetController.java @@ -0,0 +1,27 @@ +package com.moplus.moplus_server.domain.problem.controller; + +import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.problem.service.ProblemGetService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/problems") +@RequiredArgsConstructor +public class ProblemGetController { + + private final ProblemGetService problemGetService; + + @GetMapping("/{id}") + @Operation(summary = "문항 조회", description = "문항를 조회합니다.") + public ResponseEntity getProblem( + @PathVariable("id") String id + ) { + return ResponseEntity.ok(problemGetService.getProblem(id)); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSaveController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSaveController.java new file mode 100644 index 0000000..a190bad --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSaveController.java @@ -0,0 +1,39 @@ +package com.moplus.moplus_server.domain.problem.controller; + +import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; +import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.problem.service.ProblemSaveService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/problems") +@RequiredArgsConstructor +public class ProblemSaveController { + + private final ProblemSaveService problemSaveService; + + @PostMapping("") + @Operation(summary = "문항 생성", description = "문제를 생성합니다. 새끼 문항은 list 순서대로 sequence를 저장합니다.") + public ResponseEntity createProblem( + @RequestBody ProblemPostRequest request + ) { + return ResponseEntity.ok(problemSaveService.createProblem(request).toString()); + } + + @PostMapping("/{id}") + @Operation(summary = "문항 업데이트", description = "문제를 업데이트합니다. 새끼 문항은 수정된 리스트, 새로 생성된 리스트, 삭제된 리스트가 필요합니다.") + public ResponseEntity updateProblem( + @PathVariable("id") String id, + @RequestBody ProblemUpdateRequest request + ) { + return ResponseEntity.ok(problemSaveService.updateProblem(id, request)); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java new file mode 100644 index 0000000..2cb733a --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java @@ -0,0 +1,45 @@ +package com.moplus.moplus_server.domain.problem.domain; + +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Answer { + + @Column(name = "answer") + private String value; + + public Answer(String value, ProblemType problemType) { + validateByType(value, problemType); + this.value = value; + } + + private void validateByType(String answer, ProblemType problemType) { + if (answer.isBlank()) { + throw new InvalidValueException(ErrorCode.BLANK_INPUT_VALUE); + } + if (problemType == ProblemType.MULTIPLE_CHOICE) { + if (!answer.matches("^[1-5]*$")) { + throw new InvalidValueException(ErrorCode.INVALID_MULTIPLE_CHOICE_ANSWER); + } + } + if (problemType == ProblemType.SHORT_NUMBER_ANSWER) { + try { + int numericAnswer = Integer.parseInt(answer); + if (numericAnswer < 0 || numericAnswer > 999) { + throw new InvalidValueException(ErrorCode.INVALID_SHORT_NUMBER_ANSWER); + } + } catch (NumberFormatException e) { + throw new InvalidValueException(ErrorCode.INVALID_SHORT_NUMBER_ANSWER); + } + } + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/ChildProblem.java deleted file mode 100644 index 21291ef..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/ChildProblem.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.moplus.moplus_server.domain.problem.domain; - -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import java.util.Set; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@Entity -@NoArgsConstructor -public class ChildProblem { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "problem_id") - Long id; - @ElementCollection - @CollectionTable(name = "child_problem_concept", joinColumns = @JoinColumn(name = "concept_tag_id")) - Set conceptTagIds; - private String problemImageUrl; - private String answer; - - public ChildProblem(String problemImageUrl, String answer, Set conceptTagIds) { - this.problemImageUrl = problemImageUrl; - this.answer = answer; - this.conceptTagIds = conceptTagIds; - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java deleted file mode 100644 index fcaff94..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Problem.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.moplus.moplus_server.domain.problem.domain; - -import com.moplus.moplus_server.global.common.BaseEntity; -import jakarta.persistence.CascadeType; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToMany; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@Entity -@NoArgsConstructor -public class Problem extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "problem_id") - Long id; - - String practiceTestId; - int number; - int answer; - String comment; - String mainProblemImageUrl; - String mainAnalysisImageUrl; - String readingTipImageUrl; - String seniorTipImageUrl; - String prescriptionImageUrl; - - @ElementCollection - @CollectionTable(name = "problem_concept", joinColumns = @JoinColumn(name = "concept_tag_id")) - Set conceptTagIds; - - @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) - @JoinColumn(name = "problem_id") - private List childProblems = new ArrayList<>(); - - public Problem(String practiceTestId, int number, int answer, String comment, String mainProblemImageUrl, - String mainAnalysisImageUrl, String readingTipImageUrl, String seniorTipImageUrl, - String prescriptionImageUrl, Set conceptTagIds) { - this.practiceTestId = practiceTestId; - this.number = number; - this.answer = answer; - this.comment = comment; - this.mainProblemImageUrl = mainProblemImageUrl; - this.mainAnalysisImageUrl = mainAnalysisImageUrl; - this.readingTipImageUrl = readingTipImageUrl; - this.seniorTipImageUrl = seniorTipImageUrl; - this.prescriptionImageUrl = prescriptionImageUrl; - this.conceptTagIds = conceptTagIds; - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java new file mode 100644 index 0000000..7bbede9 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java @@ -0,0 +1,71 @@ +package com.moplus.moplus_server.domain.problem.domain.childProblem; + +import com.moplus.moplus_server.domain.problem.domain.Answer; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; +import com.moplus.moplus_server.global.common.BaseEntity; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import java.util.Set; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor +public class ChildProblem extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "problem_id") + Long id; + @ElementCollection + @CollectionTable(name = "child_problem_concept", joinColumns = @JoinColumn(name = "concept_tag_id")) + Set conceptTagIds; + private String imageUrl; + @Embedded + private Answer answer; + private ProblemType problemType; + private int sequence; + + @Builder + public ChildProblem(String imageUrl, ProblemType problemType, String answer, Set conceptTagIds, + int sequence) { + validateAnswerByType(answer, problemType); + this.imageUrl = imageUrl; + this.problemType = problemType; + this.answer = new Answer(answer, problemType); + this.conceptTagIds = conceptTagIds; + this.sequence = sequence; + } + + public void validateAnswerByType(String answer, ProblemType problemType) { + if (this.problemType == ProblemType.MULTIPLE_CHOICE) { + if (!answer.matches("^[1-5]*$")) { + throw new InvalidValueException(ErrorCode.INVALID_MULTIPLE_CHOICE_ANSWER); + } + } + } + + public void update(ChildProblemUpdateRequest request) { + this.imageUrl = request.imageUrl(); + this.problemType = request.problemType(); + this.answer = new Answer(request.answer(), request.problemType()); + this.conceptTagIds = request.conceptTagIds(); + this.sequence = request.sequence(); + } + + public String getAnswer() { + return answer.getValue(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTest.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTest.java new file mode 100644 index 0000000..f2f064f --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTest.java @@ -0,0 +1,34 @@ +package com.moplus.moplus_server.domain.problem.domain.practiceTest; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "practice_test_tag") +@NoArgsConstructor +public class PracticeTest { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + private int year; + private int month; + private Subject subject; + private String area; + + public PracticeTest(String name, int year, int month, Subject subject) { + this.name = name; + this.year = year; + this.month = month; + this.subject = subject; + this.area = "수학"; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java new file mode 100644 index 0000000..9dd4f16 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java @@ -0,0 +1,30 @@ +package com.moplus.moplus_server.domain.problem.domain.practiceTest; + + +import java.util.Arrays; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Subject { + + 고1("고1", 30, 100, 1), + 고2("고2", 30, 100, 2), + 미적분("미적분", 30, 100, 3), + 기하("기하", 30, 100, 4), + 확률과통계("확률과통계", 30, 100, 5), + ; + + private final String value; + private final int problemCount; + private final int perfectScore; + private final int idCode; + + public static Subject fromValue(String value) { + return Arrays.stream(Subject.values()) + .filter(subject -> subject.value.equals(value)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("해당 값에 맞는 Subject가 없습니다: " + value)); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java new file mode 100644 index 0000000..b72ceb8 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -0,0 +1,105 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import com.moplus.moplus_server.domain.problem.domain.Answer; +import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTest; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemPostRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; +import com.moplus.moplus_server.global.common.BaseEntity; +import jakarta.persistence.CascadeType; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embedded; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderBy; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor +public class Problem extends BaseEntity { + + @EmbeddedId + ProblemId id; + + Long practiceTestId; + int number; + @Embedded + Answer answer; + String comment; + String mainProblemImageUrl; + String mainAnalysisImageUrl; + String readingTipImageUrl; + String seniorTipImageUrl; + String prescriptionImageUrl; + @ElementCollection + @CollectionTable(name = "problem_concept", joinColumns = @JoinColumn(name = "concept_tag_id")) + Set conceptTagIds; + private ProblemType problemType; + private boolean isPublished; + private boolean isModified; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @JoinColumn(name = "problem_id") + @OrderBy("sequence ASC") + private List childProblems = new ArrayList<>(); + + @Builder + public Problem(ProblemId id, PracticeTest practiceTest, int number, String answer, String comment, + String mainProblemImageUrl, + String mainAnalysisImageUrl, String readingTipImageUrl, String seniorTipImageUrl, + String prescriptionImageUrl, ProblemType problemType, Set conceptTagIds, + List childProblems) { + this.id = id; + this.practiceTestId = practiceTest.getId(); + this.number = number; + this.comment = comment; + this.mainProblemImageUrl = mainProblemImageUrl; + this.mainAnalysisImageUrl = mainAnalysisImageUrl; + this.readingTipImageUrl = readingTipImageUrl; + this.seniorTipImageUrl = seniorTipImageUrl; + this.prescriptionImageUrl = prescriptionImageUrl; + this.problemType = ProblemType.getTypeForProblem(practiceTest.getSubject().getValue(), number); + this.answer = new Answer(answer, this.problemType); + this.conceptTagIds = conceptTagIds; + this.childProblems = childProblems; + this.isPublished = false; + this.isModified = false; + } + + public String getAnswer() { + return answer.getValue(); + } + + public void addChildProblem(ChildProblemPostRequest request) { + ChildProblem childProblem = ChildProblem.builder() + .imageUrl(request.imageUrl()) + .problemType(request.problemType()) + .answer(request.answer()) + .conceptTagIds(request.conceptTagIds()) + .sequence(request.sequence()) + .build(); + childProblems.add(request.sequence(), childProblem); + } + + public void updateChildProblem(ChildProblemUpdateRequest request) { + childProblems.get(request.sequence()).update(request); + } + + public void deleteChildProblem(Long childProblemId) { + childProblems.forEach(childProblem -> { + if (childProblem.getId().equals(childProblemId)) { + childProblems.remove(childProblem); + } + }); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java new file mode 100644 index 0000000..847b283 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java @@ -0,0 +1,18 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.io.Serializable; +import lombok.NoArgsConstructor; + +@Embeddable +@NoArgsConstructor +public class ProblemId implements Serializable { + + @Column(name = "problem_id") + private String id; + + public ProblemId(String id) { + this.id = id; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java new file mode 100644 index 0000000..e5daf59 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java @@ -0,0 +1,47 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTest; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ProblemIdService { + + private static final AtomicInteger SEQUENCE = new AtomicInteger(1); // XXX 값 증가를 위한 카운터 + private final ProblemRepository problemRepository; + + /* + 문제 ID 생성 로직 + AA : 과목 ( 1: 수학, 2: 영어, 3: 국어, 4: 사회, 5: 과학 ) + S : ( 1: 고1, 2: 고2, 3: 미적분, 4: 기하, 5: 확률과 통계, 6: 가형, 7: 나형 ) + YY: 년도 (두 자리) + MM: 월 (두 자리) + NN : 번호 (01~99) + C : 변형 여부 ( 0: 기본, 1: 변형 ) + XXX : 3자리 구분 숫자 + */ + public ProblemId nextId(int number, PracticeTest practiceTest) { + + int DEFAULT_AREA = 1; //현재 영역은 수학밖에 없음 + int subject = practiceTest.getSubject().getIdCode(); // AA (과목) + int year = practiceTest.getYear() % 100; // YY (두 자리 연도) + int month = practiceTest.getMonth(); // MM (두 자리 월) + int DEFAULT_MODIFIED = 0; // 변형 여부 (0: 기본, 1: 변형) + + String generatedId; + int sequence; + + // 중복되지 않는 ID 찾을 때까지 반복 + do { + sequence = SEQUENCE.getAndIncrement() % 1000; // 000~999 순환 + generatedId = String.format("%02d%d%02d%02d%02d%d%03d", + DEFAULT_AREA, subject, year, month, + number, DEFAULT_MODIFIED, sequence); + } while (problemRepository.existsById(new ProblemId(generatedId))); // ID가 이미 존재하면 재생성 + + return new ProblemId(generatedId); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java new file mode 100644 index 0000000..2410bcf --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum ProblemType { + MULTIPLE_CHOICE("객관식"), + SHORT_NUMBER_ANSWER("주관식_숫자"), + SHORT_STRING_ANSWER("주관식_문자"); + + + private final String name; + + public static ProblemType getTypeForProblem(String subject, int number) { + + // 미적분, 기하, 확률과 통계 + if (subject.equals("미적분") || subject.equals("기하") || subject.equals("확률과통계")) { + if ((number >= 1 && number <= 15) || (number >= 23 && number <= 28)) { + return MULTIPLE_CHOICE; + } else if ((number >= 16 && number <= 22) || number == 29 || number == 30) { + return SHORT_NUMBER_ANSWER; + } + } + + // 고1, 고2 + if (subject.equals("고1") || subject.equals("고2")) { + if (number >= 1 && number <= 21) { + return MULTIPLE_CHOICE; + } else if (number >= 22 && number <= 30) { + return SHORT_NUMBER_ANSWER; + } + } + + // 기본값: 객관식 + return MULTIPLE_CHOICE; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemDeleteRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemDeleteRequest.java new file mode 100644 index 0000000..07615cb --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemDeleteRequest.java @@ -0,0 +1,6 @@ +package com.moplus.moplus_server.domain.problem.dto.request; + +public record ChildProblemDeleteRequest( + Long childProblemId +) { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java new file mode 100644 index 0000000..1b42975 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java @@ -0,0 +1,13 @@ +package com.moplus.moplus_server.domain.problem.dto.request; + +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import java.util.Set; + +public record ChildProblemPostRequest( + String imageUrl, + ProblemType problemType, + String answer, + Set conceptTagIds, + int sequence +) { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java new file mode 100644 index 0000000..f8c5f35 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java @@ -0,0 +1,14 @@ +package com.moplus.moplus_server.domain.problem.dto.request; + +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import java.util.Set; + +public record ChildProblemUpdateRequest( + Long id, + String imageUrl, + ProblemType problemType, + String answer, + Set conceptTagIds, + int sequence +) { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java new file mode 100644 index 0000000..3af1adc --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.dto.request; + +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTest; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import java.util.List; +import java.util.Set; + +public record ProblemPostRequest( + Set conceptTagIds, + Long practiceTestId, + int number, + String answer, + String comment, + String mainProblemImageUrl, + String mainAnalysisImageUrl, + String readingTipImageUrl, + String seniorTipImageUrl, + String prescriptionImageUrl, + List childProblems +) { + public Problem toEntity(PracticeTest practiceTest, ProblemId problemId) { + return Problem.builder() + .id(problemId) + .conceptTagIds(conceptTagIds) + .practiceTest(practiceTest) + .number(number) + .answer(answer) + .comment(comment) + .mainProblemImageUrl(mainProblemImageUrl) + .mainAnalysisImageUrl(mainAnalysisImageUrl) + .readingTipImageUrl(readingTipImageUrl) + .seniorTipImageUrl(seniorTipImageUrl) + .prescriptionImageUrl(prescriptionImageUrl) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java new file mode 100644 index 0000000..b1a462b --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java @@ -0,0 +1,21 @@ +package com.moplus.moplus_server.domain.problem.dto.request; + +import java.util.List; +import java.util.Set; + +public record ProblemUpdateRequest( + Set conceptTagIds, + Long practiceTestId, + int number, + int answer, + String comment, + String mainProblemImageUrl, + String mainAnalysisImageUrl, + String readingTipImageUrl, + String seniorTipImageUrl, + String prescriptionImageUrl, + List updateChildProblems, + List createChildProblems, + List deleteChildProblems +) { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java new file mode 100644 index 0000000..1641d2c --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java @@ -0,0 +1,26 @@ +package com.moplus.moplus_server.domain.problem.dto.response; + +import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import java.util.Set; +import lombok.Builder; + +@Builder +public record ChildProblemGetResponse( + Long childProblemId, + String imageUrl, + ProblemType problemType, + String answer, + Set conceptTagIds +) { + + public static ChildProblemGetResponse of(ChildProblem childProblem) { + return ChildProblemGetResponse.builder() + .childProblemId(childProblem.getId()) + .imageUrl(childProblem.getImageUrl()) + .problemType(childProblem.getProblemType()) + .answer(childProblem.getAnswer()) + .conceptTagIds(childProblem.getConceptTagIds()) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java new file mode 100644 index 0000000..1947067 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java @@ -0,0 +1,42 @@ +package com.moplus.moplus_server.domain.problem.dto.response; + +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import java.util.List; +import java.util.Set; +import lombok.Builder; + +@Builder +public record ProblemGetResponse( + String problemId, + Set conceptTagIds, + Long practiceTestId, + int number, + String answer, + String comment, + String mainProblemImageUrl, + String mainAnalysisImageUrl, + String readingTipImageUrl, + String seniorTipImageUrl, + String prescriptionImageUrl, + List childProblems +) { + + public static ProblemGetResponse of(Problem problem) { + return ProblemGetResponse.builder() + .problemId(problem.getId().toString()) + .conceptTagIds(problem.getConceptTagIds()) + .practiceTestId(problem.getPracticeTestId()) + .number(problem.getNumber()) + .answer(problem.getAnswer()) + .comment(problem.getComment()) + .mainProblemImageUrl(problem.getMainProblemImageUrl()) + .mainAnalysisImageUrl(problem.getMainAnalysisImageUrl()) + .readingTipImageUrl(problem.getReadingTipImageUrl()) + .seniorTipImageUrl(problem.getSeniorTipImageUrl()) + .prescriptionImageUrl(problem.getPrescriptionImageUrl()) + .childProblems(problem.getChildProblems().stream() + .map(ChildProblemGetResponse::of) + .toList()) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java index a27fe22..be781ed 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java @@ -1,6 +1,6 @@ package com.moplus.moplus_server.domain.problem.repository; -import com.moplus.moplus_server.domain.problem.domain.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import org.springframework.data.jpa.repository.JpaRepository; public interface ChildProblemRepository extends JpaRepository { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/PracticeTestTagRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/PracticeTestTagRepository.java new file mode 100644 index 0000000..9e5a514 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/PracticeTestTagRepository.java @@ -0,0 +1,14 @@ +package com.moplus.moplus_server.domain.problem.repository; + +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTest; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PracticeTestTagRepository extends JpaRepository { + + default PracticeTest findByIdElseThrow(Long id) { + return findById(id) + .orElseThrow(() -> new NotFoundException(ErrorCode.PRACTICE_TEST_NOT_FOUND)); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java index 84c1f1f..99d0d05 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java @@ -1,7 +1,23 @@ package com.moplus.moplus_server.domain.problem.repository; -import com.moplus.moplus_server.domain.problem.domain.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.global.error.exception.AlreadyExistException; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; -public interface ProblemRepository extends JpaRepository { +public interface ProblemRepository extends JpaRepository { + + boolean existsByPracticeTestIdAndNumber(Long practiceTestId, int number); + + default void existsByPracticeTestIdAndNumberOrThrow(Long practiceTestId, int number) { + if (existsByPracticeTestIdAndNumber(practiceTestId, number)) { + throw new AlreadyExistException(ErrorCode.PROBLEM_ALREADY_EXIST); + } + } + + default Problem findByIdElseThrow(ProblemId problemId) { + return findById(problemId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java new file mode 100644 index 0000000..65dccf1 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java @@ -0,0 +1,22 @@ +package com.moplus.moplus_server.domain.problem.service; + +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProblemGetService { + + private final ProblemRepository problemRepository; + + @Transactional(readOnly = true) + public ProblemGetResponse getProblem(String problemId) { + Problem problem = problemRepository.findByIdElseThrow(new ProblemId(problemId)); + return ProblemGetResponse.of(problem); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java new file mode 100644 index 0000000..4182002 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java @@ -0,0 +1,57 @@ +package com.moplus.moplus_server.domain.problem.service; + +import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTest; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemIdService; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemDeleteRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; +import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProblemSaveService { + + private final ProblemRepository problemRepository; + private final PracticeTestTagRepository practiceTestRepository; + private final ConceptTagRepository conceptTagRepository; + private final ProblemIdService problemIdService; + + @Transactional + public ProblemId createProblem(ProblemPostRequest request) { + PracticeTest practiceTest = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); + problemRepository.existsByPracticeTestIdAndNumberOrThrow(practiceTest.getId(), request.number()); + conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); + + ProblemId problemId = problemIdService.nextId(request.number(), practiceTest); + Problem problem = request.toEntity(practiceTest, problemId); + request.childProblems() + .forEach(problem::addChildProblem); + + return problemRepository.save(problem).getId(); + } + + @Transactional + public ProblemGetResponse updateProblem(String problemId, ProblemUpdateRequest request) { + PracticeTest practiceTest = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); + problemRepository.existsByPracticeTestIdAndNumberOrThrow(practiceTest.getId(), request.number()); + conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); + + Problem problem = problemRepository.findByIdElseThrow(new ProblemId(problemId)); + request.deleteChildProblems().stream() + .map(ChildProblemDeleteRequest::childProblemId) + .forEach(problem::deleteChildProblem); + request.createChildProblems().forEach(problem::addChildProblem); + request.updateChildProblems().forEach(problem::updateChildProblem); + + Problem savedProblem = problemRepository.save(problem); + return ProblemGetResponse.of(savedProblem); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java index 085cfad..1382c1f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/DetailResultApplication/service/DetailResultApplicationService.java @@ -14,7 +14,7 @@ import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.time.Duration; @@ -33,7 +33,7 @@ public class DetailResultApplicationService { private final EstimatedRatingRepository estimatedRatingRepository; private final IncorrectProblemService incorrectProblemService; private final IncorrectProblemRepository incorrectProblemRepository; - private final ProblemRepository problemRepository; + private final ProblemForTestRepository problemForTestRepository; @Transactional public void saveApplication(DetailResultApplicationPostRequest request) { @@ -57,7 +57,7 @@ public ReviewNoteGetResponse getReviewNoteInfo(Long testResultId) { List incorrectProblemForTests = incorrectProblemRepository.findAllByTestResultId(testResultId) .stream() .map(IncorrectProblem::getProblemId) - .map(problemId -> problemRepository.findById(problemId).orElseThrow()) + .map(problemId -> problemForTestRepository.findById(problemId).orElseThrow()) .toList(); List forCurrentRating = incorrectProblemForTests.stream() diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java index d40720a..92be481 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java @@ -1,5 +1,6 @@ package com.moplus.moplus_server.domain.v0.practiceTest.domain; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.Subject; import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.PracticeTestRequest; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.Column; @@ -30,7 +31,8 @@ public class PracticeTest extends BaseEntity { private long viewCount = 0L; private int solvesCount = 0; - private String publicationYear; + private int publicationYear; + private int month = 0; @Enumerated(EnumType.STRING) private Subject subject; @@ -38,15 +40,18 @@ public class PracticeTest extends BaseEntity { private Duration averageSolvingTime = Duration.ZERO; @Builder - public PracticeTest(String name, String round, String provider, String publicationYear, Subject subject) { + public PracticeTest(String name, String round, String provider, long viewCount, int solvesCount, + int publicationYear, + int month, Subject subject, Duration averageSolvingTime) { this.name = name; this.round = round; this.provider = provider; - this.viewCount = 0; - this.solvesCount = 0; + this.viewCount = viewCount; + this.solvesCount = solvesCount; this.publicationYear = publicationYear; + this.month = month; this.subject = subject; - this.averageSolvingTime = Duration.ZERO; + this.averageSolvingTime = averageSolvingTime; } public void updateByPracticeTestRequest(PracticeTestRequest request) { diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/RatingTable.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/RatingTable.java index 611885e..5ac79c2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/RatingTable.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/RatingTable.java @@ -1,5 +1,6 @@ package com.moplus.moplus_server.domain.v0.practiceTest.domain; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.Subject; import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.RatingTableRequest; import com.moplus.moplus_server.domain.v0.practiceTest.repository.converter.RatingRowConverter; import com.moplus.moplus_server.global.common.BaseEntity; diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/Subject.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/Subject.java deleted file mode 100644 index 5b460b4..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/Subject.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.moplus.moplus_server.domain.v0.practiceTest.domain; - - -import java.util.Arrays; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public enum Subject { - - // 화법과작문("화법과작문", 45, 100), -// 언어와매체("언어와매체", 45, 100), - 고1("고1", 30, 100), - 고2("고2", 30, 100), - 미적분("미적분", 30, 100), - 확률과통계("확률과통계", 30, 100), - 기하("기하", 30, 100); -// 영어("영어",45, 100), -// 물리I("물리I",20, 50), -// 화학I("화학I",20, 50), -// 생명과학I("생명과학I",20, 50), -// 지구과학I("지구과학I",20, 50), -// 물리II("물리II",20, 50), -// 화학II("화학II",20, 50), -// 생명과학II("생명과학II",20, 50), -// 지구과학II("지구과학II",20, 50), -// 한국지리("한국지리",20, 50), -// 세계지리("세계지리",20, 50), -// 동아시아사("동아시아사",20, 50), -// 생활과윤리("생활과윤리",20, 50), -// 윤리와사상("윤리와사상",20, 50), -// 사회문화("사회문화",20, 50), -// 정치와법("정치와법",20, 50), -// 경제("경제",20, 50); - - private final String value; - private final int problemCount; - private final int perfectScore; - - public static Subject fromValue(String value) { - return Arrays.stream(Subject.values()) - .filter(subject -> subject.value.equals(value)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("해당 값에 맞는 Subject가 없습니다: " + value)); - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/PracticeTestRequest.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/PracticeTestRequest.java index b2c4a5e..b27fb95 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/PracticeTestRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/dto/admin/request/PracticeTestRequest.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.Subject; import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.v0.practiceTest.domain.Subject; import java.util.ArrayList; import java.util.List; import lombok.Getter; @@ -16,11 +16,11 @@ public class PracticeTestRequest { private String name; private String round; private String provider; - private String publicationYear; + private int publicationYear; private String subject; private List ratingTables = new ArrayList<>(); - public PracticeTestRequest(Long id, String name, String round, String provider, String publicationYear, + public PracticeTestRequest(Long id, String name, String round, String provider, int publicationYear, String subject, List ratingTables) { this.id = id; this.name = name; @@ -51,7 +51,7 @@ public static PracticeTestRequest getUpdateModelObject(PracticeTest practiceTest } public static PracticeTestRequest getCreateModelObject() { - return new PracticeTestRequest(null, "", "", "", "", null, RatingTableRequest.getDefaultRatingTableRequest()); + return new PracticeTestRequest(null, "", "", "", 0, null, RatingTableRequest.getDefaultRatingTableRequest()); } public PracticeTest toEntity() { diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java index 948dc4e..fc3b4f9 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java @@ -14,8 +14,4 @@ public interface PracticeTestRepository extends JpaRepository { +public interface ProblemForTestRepository extends JpaRepository { List findAllByPracticeTestId(Long id); diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java index 4260692..e2d7aab 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/PracticeTestAdminService.java @@ -1,12 +1,12 @@ package com.moplus.moplus_server.domain.v0.practiceTest.service.admin; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.Subject; import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; import com.moplus.moplus_server.domain.v0.practiceTest.domain.RatingTable; -import com.moplus.moplus_server.domain.v0.practiceTest.domain.Subject; import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.PracticeTestRequest; import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.RatingTableRequest; import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; import com.moplus.moplus_server.domain.v0.practiceTest.repository.RatingTableRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; @@ -22,7 +22,7 @@ public class PracticeTestAdminService { private final PracticeTestRepository practiceTestRepository; private final RatingTableRepository ratingTableRepository; - private final ProblemRepository problemRepository; + private final ProblemForTestRepository problemForTestRepository; private final RatingTableAdminService ratingTableAdminService; private static void addToPracticeTestUpdateModel(Model model, List ratingTables, @@ -63,7 +63,7 @@ public void getPracticeTestUpdateModel(Model model, Long id) { @Transactional public void deletePracticeTest(Long id) { ratingTableRepository.deleteAllByPracticeTestId(id); - problemRepository.deleteAllByPracticeTestId(id); + problemForTestRepository.deleteAllByPracticeTestId(id); practiceTestRepository.deleteById(id); } diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java index affbdff..411f1e1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/admin/ProblemImageUploadService.java @@ -6,8 +6,8 @@ import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemImageForTest; import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.ProblemImageRequest; import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemImageRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import com.moplus.moplus_server.global.utils.s3.S3Util; @@ -26,12 +26,13 @@ public class ProblemImageUploadService { private final S3Util s3Util; private final PracticeTestRepository practiceTestRepository; - private final ProblemRepository problemRepository; + private final ProblemForTestRepository problemForTestRepository; private final ProblemImageRepository problemImageRepository; @Transactional(readOnly = true) public void setProblemImagesByPracticeTestId(Long practiceTestId, Model model) { - List imageRequests = problemRepository.findAllByPracticeTestId(practiceTestId).stream() + List imageRequests = problemForTestRepository.findAllByPracticeTestId(practiceTestId) + .stream() .map(ProblemImageRequest::of) .toList(); model.addAttribute("problemImageRequests", imageRequests); @@ -41,7 +42,7 @@ public void setProblemImagesByPracticeTestId(Long practiceTestId, Model model) { public void uploadImage(Long practiceId, Long problemId, MultipartFile image) { PracticeTest practiceTest = practiceTestRepository.findById(practiceId) .orElseThrow(() -> new NotFoundException(ErrorCode.PRACTICE_TEST_NOT_FOUND)); - ProblemForTest problemForTest = problemRepository.findById(problemId) + ProblemForTest problemForTest = problemForTestRepository.findById(problemId) .orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); String fileName = uploadFile(image, problemId, practiceTest.getName()); String s3ObjectUrl = s3Util.getS3ObjectUrl(fileName); @@ -52,7 +53,7 @@ public void uploadImage(Long practiceId, Long problemId, MultipartFile image) { .build(); ProblemImageForTest saved = problemImageRepository.save(problemImageForTest); problemForTest.addImage(saved); - problemRepository.save(problemForTest); + problemForTestRepository.save(problemForTest); } public String uploadFile(MultipartFile file, Long problemId, String practiceTestName) { diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/PracticeTestService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/PracticeTestService.java index 8a3a9a6..9f99f8a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/PracticeTestService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/PracticeTestService.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.v0.practiceTest.service.client; -import com.moplus.moplus_server.domain.v0.practiceTest.dto.client.response.PracticeTestGetResponse; import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; +import com.moplus.moplus_server.domain.v0.practiceTest.dto.client.response.PracticeTestGetResponse; import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java index b22ff8b..e5560a6 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/service/client/ProblemService.java @@ -4,7 +4,7 @@ import com.moplus.moplus_server.domain.v0.practiceTest.domain.ProblemForTest; import com.moplus.moplus_server.domain.v0.practiceTest.dto.admin.request.ProblemCreateRequest; import com.moplus.moplus_server.domain.v0.practiceTest.dto.client.response.ProblemGetResponse; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import jakarta.servlet.http.HttpServletRequest; @@ -18,7 +18,7 @@ @RequiredArgsConstructor public class ProblemService { - private final ProblemRepository problemRepository; + private final ProblemForTestRepository problemForTestRepository; @Transactional public void saveProblems(PracticeTest practiceTest, HttpServletRequest request) { @@ -41,12 +41,12 @@ public void saveProblems(PracticeTest practiceTest, HttpServletRequest request) .toList(); problemsEntities .forEach(ProblemForTest::calculateProblemRating); - problemRepository.saveAll(problemsEntities); + problemForTestRepository.saveAll(problemsEntities); } @Transactional public void updateProblems(PracticeTest practiceTest, HttpServletRequest request) { - List problemForTests = problemRepository.findAllByPracticeTestId(practiceTest.getId()); + List problemForTests = problemForTestRepository.findAllByPracticeTestId(practiceTest.getId()); for (int i = 1; i <= practiceTest.getSubject().getProblemCount(); i++) { ProblemForTest problemForTest = problemForTests.get(i - 1); @@ -55,29 +55,29 @@ public void updateProblems(PracticeTest practiceTest, HttpServletRequest request problemForTest.updateCorrectRate(Double.parseDouble(request.getParameter("correctRate_" + i))); problemForTest.calculateProblemRating(); - problemRepository.save(problemForTest); + problemForTestRepository.save(problemForTest); } } public ProblemForTest getProblemByPracticeTestIdAndNumber(Long practiceId, String problemNumber) { - return problemRepository.findByProblemNumberAndPracticeTestId(problemNumber, practiceId) + return problemForTestRepository.findByProblemNumberAndPracticeTestId(problemNumber, practiceId) .orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); } @Transactional public ProblemForTest updateCorrectRate(Long practiceTestId, String problemNumber, double correctRate) { - ProblemForTest problemForTest = problemRepository.findByProblemNumberAndPracticeTestIdWithPessimisticLock( + ProblemForTest problemForTest = problemForTestRepository.findByProblemNumberAndPracticeTestIdWithPessimisticLock( problemNumber, practiceTestId) .orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); problemForTest.getPracticeTest(); problemForTest.updateCorrectRate(correctRate); - return problemRepository.save(problemForTest); + return problemForTestRepository.save(problemForTest); } public List getProblemsByTestId(Long testId) { - return problemRepository.findAllByPracticeTestId(testId).stream() + return problemForTestRepository.findAllByPracticeTestId(testId).stream() .map(ProblemGetResponse::from) .toList(); } diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/AlreadyExistException.java b/src/main/java/com/moplus/moplus_server/global/error/exception/AlreadyExistException.java new file mode 100644 index 0000000..31f1405 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/AlreadyExistException.java @@ -0,0 +1,7 @@ +package com.moplus.moplus_server.global.error.exception; + +public class AlreadyExistException extends BusinessException { + public AlreadyExistException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index b8c892f..3968ed9 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -10,6 +10,7 @@ public enum ErrorCode { INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류, 관리자에게 문의하세요"), INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "잘못된 입력 값입니다"), BAD_CREDENTIALS(HttpStatus.UNAUTHORIZED, "잘못된 인증 정보입니다"), + BLANK_INPUT_VALUE(HttpStatus.BAD_REQUEST, "빈 값이 입력되었습니다"), //Auth AUTH_NOT_FOUND(HttpStatus.UNAUTHORIZED, "시큐리티 인증 정보를 찾을 수 없습니다."), @@ -26,8 +27,14 @@ public enum ErrorCode { //모의고사 PRACTICE_TEST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 모의고사를 찾을 수 없습니다"), - //문제 + //문항 PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 문제를 찾을 수 없습니다"), + PROBLEM_ALREADY_EXIST(HttpStatus.CONFLICT, "해당 문제는 이미 존재합니다"), + INVALID_MULTIPLE_CHOICE_ANSWER(HttpStatus.BAD_REQUEST, "객관식 문제의 정답은 1~5 사이의 숫자여야 합니다"), + INVALID_SHORT_NUMBER_ANSWER(HttpStatus.BAD_REQUEST, "주관식 문제의 정답은 0~999 사이의 숫자여야 합니다"), + + //개념태그 + CONCEPT_TAG_NOT_FOUND_IN_LIST(HttpStatus.NOT_FOUND, "해당 리스트 중 존재하지 않는 개념 태그가 있습니다."), //시험결과 TEST_RESULT_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 시험 결과지를 찾을 수 없습니다"), diff --git a/src/main/resources/templates/answerInputForm.html b/src/main/resources/templates/answerInputForm.html index bf73a3f..6fd3d02 100644 --- a/src/main/resources/templates/answerInputForm.html +++ b/src/main/resources/templates/answerInputForm.html @@ -8,7 +8,7 @@ + function confirmDeletion(event) { + event.preventDefault(); + const confirmDelete = confirm("정말 삭제하시겠습니까?"); + if (confirmDelete) { + event.target.closest("form").submit(); + } + } +
- + - + -
    -
  • -
    -
    - 모의고사 이미지 -
    - -
    - - -
    -
    -
  • -
+
    +
  • +
    +
    + 모의고사 이미지 +
    + +
    + + +
    +
    +
  • +
diff --git a/src/main/resources/templates/testInputForm.html b/src/main/resources/templates/testInputForm.html index 1e08913..1666a8a 100644 --- a/src/main/resources/templates/testInputForm.html +++ b/src/main/resources/templates/testInputForm.html @@ -1,210 +1,219 @@ - Practice Test 등록 - - + + - /* 표 스타일 */ - table { - width: 100%; - border-collapse: collapse; - margin-bottom: 20px; - } +

Practice Test 등록

- table, th, td { - border: 1px solid #ccc; - } +
+
+ + +
- th, td { - padding: 8px; - text-align: center; - } +
+ + +
- th { - background-color: #f3f3f3; - } - - - +
+ + +
-

Practice Test 등록

+
+ + +
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
-
- - - -
+
+ +
+
+ + + +
+
-
- - -
-
- + + +
+
+ +
-
- + diff --git a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java index a39a9cd..5a639ed 100644 --- a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java @@ -4,7 +4,7 @@ import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemRepository; +import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; import com.moplus.moplus_server.domain.v0.practiceTest.service.client.PracticeTestService; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -25,7 +25,7 @@ class PracticeTestServiceTest { @Autowired private PracticeTestRepository practiceTestRepository; @Autowired - private ProblemRepository problemRepository; + private ProblemForTestRepository problemForTestRepository; @BeforeEach void setup() { diff --git a/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java b/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java index bd942a5..67d5298 100644 --- a/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java +++ b/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java @@ -1,24 +1,23 @@ package com.moplus.moplus_server.global.scheduler; +import static org.mockito.Mockito.when; + +import com.moplus.moplus_server.domain.problem.domain.practiceTest.Subject; import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; import com.moplus.moplus_server.domain.v0.TestResult.repository.TestResultRepository; import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.v0.practiceTest.domain.Subject; import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; import java.lang.reflect.Field; +import java.time.Duration; +import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; - -import java.time.Duration; -import java.util.List; import org.mockito.junit.jupiter.MockitoExtension; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class TestResultSchedulerTest { From 43c8501f50d6f1b5f9e2d3bdbc5026f55596ba75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 30 Jan 2025 21:13:20 +0900 Subject: [PATCH 028/115] =?UTF-8?q?[feat/#23]=20mapStruct=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20dto=20->=20=EA=B0=9D=EC=B2=B4=20mapper=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PracticeTestTagController.java | 2 + ...Controller.java => ProblemController.java} | 0 .../controller/ProblemGetController.java | 27 ------ ...PracticeTest.java => PracticeTestTag.java} | 0 .../dto/response/PracticeTestTagResponse.java | 2 + .../problem/service/ProblemDeleteService.java | 2 + .../problem/service/ProblemUpdateService.java | 2 + .../service/mapper/ChildProblemMapper.java | 14 +++ .../problem/service/mapper/ProblemMapper.java | 26 ++++++ .../global/scheduler/TestResultScheduler.java | 59 ------------- .../client/PracticeTestServiceTest.java | 58 ------------ .../domain/problem/ProblemIdServiceTest.java | 4 + .../service/ProblemSaveServiceTest.java | 4 + .../service/ProblemUpdateServiceTest.java | 4 + .../scheduler/TestResultSchedulerTest.java | 88 ------------------- src/test/resources/concept-tag.sql | 0 src/test/resources/insert-problem.sql | 0 src/test/resources/practice-test-tag.sql | 2 + 18 files changed, 62 insertions(+), 232 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java rename src/main/java/com/moplus/moplus_server/domain/problem/controller/{ProblemSaveController.java => ProblemController.java} (100%) delete mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemGetController.java rename src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/{PracticeTest.java => PracticeTestTag.java} (100%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java delete mode 100644 src/main/java/com/moplus/moplus_server/global/scheduler/TestResultScheduler.java delete mode 100644 src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java create mode 100644 src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java create mode 100644 src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java create mode 100644 src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java delete mode 100644 src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java create mode 100644 src/test/resources/concept-tag.sql create mode 100644 src/test/resources/insert-problem.sql create mode 100644 src/test/resources/practice-test-tag.sql diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java new file mode 100644 index 0000000..d630855 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java @@ -0,0 +1,2 @@ +package com.moplus.moplus_server.domain.problem.controller;public class PracticeTestTagController { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSaveController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java similarity index 100% rename from src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSaveController.java rename to src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemGetController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemGetController.java deleted file mode 100644 index bf465bf..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemGetController.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.moplus.moplus_server.domain.problem.controller; - -import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; -import com.moplus.moplus_server.domain.problem.service.ProblemGetService; -import io.swagger.v3.oas.annotations.Operation; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/v1/problems") -@RequiredArgsConstructor -public class ProblemGetController { - - private final ProblemGetService problemGetService; - - @GetMapping("/{id}") - @Operation(summary = "문항 조회", description = "문항를 조회합니다.") - public ResponseEntity getProblem( - @PathVariable("id") String id - ) { - return ResponseEntity.ok(problemGetService.getProblem(id)); - } -} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTest.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java similarity index 100% rename from src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTest.java rename to src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java new file mode 100644 index 0000000..a623b73 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java @@ -0,0 +1,2 @@ +package com.moplus.moplus_server.domain.problem.dto.response;public record PracticeTestTagResponse() { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java new file mode 100644 index 0000000..3d465ab --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java @@ -0,0 +1,2 @@ +package com.moplus.moplus_server.domain.problem.service;public class ProblemDeleteService { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java new file mode 100644 index 0000000..fc078cc --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java @@ -0,0 +1,2 @@ +package com.moplus.moplus_server.domain.problem.service;public class ProblemUpdateService { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java new file mode 100644 index 0000000..35da321 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java @@ -0,0 +1,14 @@ +package com.moplus.moplus_server.domain.problem.service; + +import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemPostRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface ChildProblemMapper { + + ChildProblem from(ChildProblemPostRequest request); + + ChildProblem from(ChildProblemUpdateRequest request); +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java new file mode 100644 index 0000000..882c71f --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java @@ -0,0 +1,26 @@ +package com.moplus.moplus_server.domain.problem.service; + +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +@Mapper(componentModel = "spring") +public interface ProblemMapper { + + @Mappings({ + @Mapping(target = "id", source = "problemId"), + @Mapping(target = "practiceTestTag", source = "practiceTestTag"), + }) + Problem from(ProblemPostRequest request, ProblemId problemId, PracticeTestTag practiceTestTag); + + @Mappings({ + @Mapping(target = "id", source = "problemId"), + @Mapping(target = "practiceTestTag", source = "practiceTestTag"), + }) + Problem from(ProblemUpdateRequest request, ProblemId problemId, PracticeTestTag practiceTestTag); +} diff --git a/src/main/java/com/moplus/moplus_server/global/scheduler/TestResultScheduler.java b/src/main/java/com/moplus/moplus_server/global/scheduler/TestResultScheduler.java deleted file mode 100644 index 4a453ae..0000000 --- a/src/main/java/com/moplus/moplus_server/global/scheduler/TestResultScheduler.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.moplus.moplus_server.global.scheduler; - -import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; -import com.moplus.moplus_server.domain.v0.TestResult.repository.TestResultRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; -import java.time.Duration; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class TestResultScheduler { - - private final PracticeTestRepository practiceTestRepository; - private final TestResultRepository testResultRepository; - - - // 5분마다 실행 (cron 표현식을 사용해 5분마다 스케줄링) - @Scheduled(cron = "0 */5 * * * *") - public void calculateAverageSolvingTime() { - List practiceTests = practiceTestRepository.findAll(); - - for (PracticeTest practiceTest : practiceTests) { - if (practiceTest.getSolvesCount() == 0) { - continue; - } - - Duration sum = Duration.ZERO; - List allByPracticeTestId = - testResultRepository.findAllByPracticeTestId(practiceTest.getId()); - - long validCount = 0; - - for (TestResult testResult : allByPracticeTestId) { - Duration solvingTime = testResult.getSolvingTime(); - - // solvingTime이 null이거나 0초일 경우는 제외 - if (solvingTime != null && !solvingTime.isZero()) { - sum = sum.plus(solvingTime); // Duration 객체는 불변이므로 새로운 객체로 할당 - validCount++; // 유효한 solvingTime이 있을 때만 카운트 증가 - } - } - - if (validCount > 0) { - // 유효한 solvingTime이 있는 경우만 평균 계산 - Duration average = sum.dividedBy(validCount); - - // 초 단위까지 포함한 average를 저장 - practiceTest.updateAverageSolvingTime(average); - practiceTestRepository.save(practiceTest); - } - System.out.println( - "평균 풀이 시간 계산 완료 : " + practiceTest.getId() + "L, 평균 시간 " + practiceTest.getAverageSolvingTime()); - } - } -} diff --git a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java deleted file mode 100644 index 5a639ed..0000000 --- a/src/test/java/com/moplus/moplus_server/domain/practiceTest/service/client/PracticeTestServiceTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.moplus.moplus_server.domain.practiceTest.service.client; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.ProblemForTestRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.service.client.PracticeTestService; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@ActiveProfiles("h2test") -class PracticeTestServiceTest { - - @Autowired - private PracticeTestService practiceTestService; - - @Autowired - private PracticeTestRepository practiceTestRepository; - @Autowired - private ProblemForTestRepository problemForTestRepository; - - @BeforeEach - void setup() { - PracticeTest practiceTest = new PracticeTest(); - practiceTestRepository.save(practiceTest); - } - - @Test - public void 동시에_조회수가_정상적으로_업데이트_되어야한다() throws InterruptedException { - Long practiceTestId = 1L; - int threadCount = 100; - ExecutorService executorService = Executors.newFixedThreadPool(36); - CountDownLatch countDownLatch = new CountDownLatch(threadCount); - - for (int i = 0; i < threadCount; i++) { - executorService.submit(() -> { - try { - practiceTestService.updateViewCount(practiceTestId); - } finally { - countDownLatch.countDown(); - } - }); - } - countDownLatch.await(); - - PracticeTest practiceTest = practiceTestRepository.findById(practiceTestId).orElseThrow(); - assertEquals(threadCount, practiceTest.getViewCount()); - } - -} \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java new file mode 100644 index 0000000..30e0157 --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java @@ -0,0 +1,4 @@ +import static org.junit.jupiter.api.Assertions.*; +class ProblemIdServiceTest { + +} \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java new file mode 100644 index 0000000..2fcce6b --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java @@ -0,0 +1,4 @@ +import static org.junit.jupiter.api.Assertions.*; +class ProblemSaveServiceTest { + +} \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java new file mode 100644 index 0000000..3231e35 --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java @@ -0,0 +1,4 @@ +import static org.junit.jupiter.api.Assertions.*; +class ProblemUpdateServiceTest { + +} \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java b/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java deleted file mode 100644 index 67d5298..0000000 --- a/src/test/java/com/moplus/moplus_server/global/scheduler/TestResultSchedulerTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.moplus.moplus_server.global.scheduler; - -import static org.mockito.Mockito.when; - -import com.moplus.moplus_server.domain.problem.domain.practiceTest.Subject; -import com.moplus.moplus_server.domain.v0.TestResult.entity.TestResult; -import com.moplus.moplus_server.domain.v0.TestResult.repository.TestResultRepository; -import com.moplus.moplus_server.domain.v0.practiceTest.domain.PracticeTest; -import com.moplus.moplus_server.domain.v0.practiceTest.repository.PracticeTestRepository; -import java.lang.reflect.Field; -import java.time.Duration; -import java.util.List; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class TestResultSchedulerTest { - - @Mock - private PracticeTestRepository practiceTestRepository; - - @Mock - private TestResultRepository testResultRepository; - - @InjectMocks - private TestResultScheduler testResultScheduler; // calculateAverageSolvingTime 메서드를 가진 클래스 - - private PracticeTest practiceTest; - private TestResult testResult1; - private TestResult testResult2; - - @BeforeEach - void setUp() throws NoSuchFieldException, IllegalAccessException { - // PracticeTest 객체 초기화 - practiceTest = PracticeTest.builder() - .name("Sample Test") - .round("1st Round") - .provider("Provider A") - .publicationYear("2024") - .subject(Subject.미적분) - .build(); - Field idField = PracticeTest.class.getDeclaredField("id"); - idField.setAccessible(true); // private 필드 접근 허용 - idField.set(practiceTest, 1L); // - - // TestResult 객체 초기화 (각 테스트의 풀이 시간을 설정) - testResult1 = TestResult.builder() - .score(85) - .solvingTime(Duration.ofMinutes(30)) // 30분 걸림 - .practiceTestId(practiceTest.getId()) - .build(); - practiceTest.plus1SolvesCount(); - - testResult2 = TestResult.builder() - .score(90) - .solvingTime(Duration.ofMinutes(45)) // 45분 걸림 - .practiceTestId(practiceTest.getId()) - .build(); - practiceTest.plus1SolvesCount(); - } - - @Test - void 평균시간계산() { - - when(practiceTestRepository.findAll()).thenReturn(List.of(practiceTest)); - List testResultsForPracticeTest1 = List.of(testResult1, testResult2); - when(testResultRepository.findAllByPracticeTestId(1L)).thenReturn(testResultsForPracticeTest1); - - // 메서드 실행 - testResultScheduler.calculateAverageSolvingTime(); - - // 검증 - long totalSeconds = testResult1.getSolvingTime().getSeconds() + testResult2.getSolvingTime().getSeconds(); - - long averageSeconds = totalSeconds / 2; - - Duration expectedAverage = Duration.ofSeconds(averageSeconds); - System.out.println(expectedAverage); - - Assertions.assertEquals(practiceTest.getAverageSolvingTime(), expectedAverage); - - } -} \ No newline at end of file diff --git a/src/test/resources/concept-tag.sql b/src/test/resources/concept-tag.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/test/resources/insert-problem.sql b/src/test/resources/insert-problem.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/test/resources/practice-test-tag.sql b/src/test/resources/practice-test-tag.sql new file mode 100644 index 0000000..a3ae1b6 --- /dev/null +++ b/src/test/resources/practice-test-tag.sql @@ -0,0 +1,2 @@ +INSERT INTO practice_test_tag (name, test_year, test_month, subject, area) +VALUES ('2025년 5월 고2 모의고사', 2024, 5, '고2', '수학'); \ No newline at end of file From 4f072605f872c79f66205c00eb72c9adcde63c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 30 Jan 2025 21:54:26 +0900 Subject: [PATCH 029/115] =?UTF-8?q?[fix/#23]=20insert-problem.sql=EC=97=90?= =?UTF-8?q?=20childProblem=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20sql=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 + .../mapper/ChildProblemMapperImpl.java | 58 +++++++ .../service/mapper/ProblemMapperImpl.java | 75 +++++++++ .../controller/PracticeTestTagController.java | 26 ++- .../problem/controller/ProblemController.java | 31 +++- .../domain/childProblem/ChildProblem.java | 21 +-- .../domain/practiceTest/PracticeTestTag.java | 10 +- .../problem/domain/problem/Problem.java | 79 +++++---- .../problem/domain/problem/ProblemId.java | 2 + .../domain/problem/ProblemIdService.java | 20 +-- .../request/ChildProblemUpdateRequest.java | 2 + .../dto/request/ProblemPostRequest.java | 6 +- .../dto/request/ProblemUpdateRequest.java | 5 +- .../dto/response/PracticeTestTagResponse.java | 15 +- .../repository/ChildProblemRepository.java | 6 + .../repository/PracticeTestTagRepository.java | 6 +- .../problem/repository/ProblemRepository.java | 7 + .../problem/service/ProblemDeleteService.java | 20 ++- .../problem/service/ProblemSaveService.java | 43 ++--- .../problem/service/ProblemUpdateService.java | 61 ++++++- .../service/mapper/ChildProblemMapper.java | 2 +- .../problem/service/mapper/ProblemMapper.java | 2 +- .../v0/practiceTest/domain/PracticeTest.java | 7 +- .../repository/PracticeTestRepository.java | 2 +- .../global/common/BaseEntity.java | 6 - .../global/error/exception/ErrorCode.java | 3 + .../domain/problem/ProblemIdServiceTest.java | 78 ++++++++- .../service/ProblemSaveServiceTest.java | 150 +++++++++++++++++- .../service/ProblemUpdateServiceTest.java | 112 ++++++++++++- src/test/resources/auth-test-data.sql | 4 +- src/test/resources/concept-tag.sql | 12 ++ src/test/resources/insert-problem.sql | 29 ++++ 32 files changed, 790 insertions(+), 115 deletions(-) create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java diff --git a/build.gradle b/build.gradle index 47c4de6..f4e44f9 100644 --- a/build.gradle +++ b/build.gradle @@ -56,6 +56,11 @@ dependencies { // validator implementation 'commons-validator:commons-validator:1.7' + // Map Struct + implementation 'org.mapstruct:mapstruct:1.6.3' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' + annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0' + } tasks.named('test') { diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java new file mode 100644 index 0000000..e76d12e --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java @@ -0,0 +1,58 @@ +package com.moplus.moplus_server.domain.problem.service.mapper; + +import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemPostRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.annotation.processing.Generated; +import org.springframework.stereotype.Component; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2025-01-30T21:11:35+0900", + comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" +) +@Component +public class ChildProblemMapperImpl implements ChildProblemMapper { + + @Override + public ChildProblem from(ChildProblemPostRequest request) { + if ( request == null ) { + return null; + } + + ChildProblem.ChildProblemBuilder childProblem = ChildProblem.builder(); + + childProblem.imageUrl( request.imageUrl() ); + childProblem.problemType( request.problemType() ); + childProblem.answer( request.answer() ); + Set set = request.conceptTagIds(); + if ( set != null ) { + childProblem.conceptTagIds( new LinkedHashSet( set ) ); + } + childProblem.sequence( request.sequence() ); + + return childProblem.build(); + } + + @Override + public ChildProblem from(ChildProblemUpdateRequest request) { + if ( request == null ) { + return null; + } + + ChildProblem.ChildProblemBuilder childProblem = ChildProblem.builder(); + + childProblem.imageUrl( request.imageUrl() ); + childProblem.problemType( request.problemType() ); + childProblem.answer( request.answer() ); + Set set = request.conceptTagIds(); + if ( set != null ) { + childProblem.conceptTagIds( new LinkedHashSet( set ) ); + } + childProblem.sequence( request.sequence() ); + + return childProblem.build(); + } +} diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java new file mode 100644 index 0000000..dc059cb --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -0,0 +1,75 @@ +package com.moplus.moplus_server.domain.problem.service.mapper; + +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.annotation.processing.Generated; +import org.springframework.stereotype.Component; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2025-01-30T21:11:35+0900", + comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" +) +@Component +public class ProblemMapperImpl implements ProblemMapper { + + @Override + public Problem from(ProblemPostRequest request, ProblemId problemId, PracticeTestTag practiceTestTag) { + if ( request == null && problemId == null && practiceTestTag == null ) { + return null; + } + + Problem.ProblemBuilder problem = Problem.builder(); + + if ( request != null ) { + problem.number( request.number() ); + problem.answer( request.answer() ); + problem.comment( request.comment() ); + problem.mainProblemImageUrl( request.mainProblemImageUrl() ); + problem.mainAnalysisImageUrl( request.mainAnalysisImageUrl() ); + problem.readingTipImageUrl( request.readingTipImageUrl() ); + problem.seniorTipImageUrl( request.seniorTipImageUrl() ); + problem.prescriptionImageUrl( request.prescriptionImageUrl() ); + Set set = request.conceptTagIds(); + if ( set != null ) { + problem.conceptTagIds( new LinkedHashSet( set ) ); + } + } + problem.id( problemId ); + problem.practiceTestTag( practiceTestTag ); + + return problem.build(); + } + + @Override + public Problem from(ProblemUpdateRequest request, ProblemId problemId, PracticeTestTag practiceTestTag) { + if ( request == null && problemId == null && practiceTestTag == null ) { + return null; + } + + Problem.ProblemBuilder problem = Problem.builder(); + + if ( request != null ) { + problem.answer( String.valueOf( request.answer() ) ); + problem.comment( request.comment() ); + problem.mainProblemImageUrl( request.mainProblemImageUrl() ); + problem.mainAnalysisImageUrl( request.mainAnalysisImageUrl() ); + problem.readingTipImageUrl( request.readingTipImageUrl() ); + problem.seniorTipImageUrl( request.seniorTipImageUrl() ); + problem.prescriptionImageUrl( request.prescriptionImageUrl() ); + Set set = request.conceptTagIds(); + if ( set != null ) { + problem.conceptTagIds( new LinkedHashSet( set ) ); + } + } + problem.id( problemId ); + problem.practiceTestTag( practiceTestTag ); + + return problem.build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java index d630855..443b7bc 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java @@ -1,2 +1,26 @@ -package com.moplus.moplus_server.domain.problem.controller;public class PracticeTestTagController { +package com.moplus.moplus_server.domain.problem.controller; + +import com.moplus.moplus_server.domain.problem.dto.response.PracticeTestTagResponse; +import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/practiceTestTags") +@RequiredArgsConstructor +public class PracticeTestTagController { + + private final PracticeTestTagRepository practiceTestTagRepository; + + @GetMapping("") + public ResponseEntity> getPracticeTestTags() { + List responses = practiceTestTagRepository.findAll().stream() + .map(PracticeTestTagResponse::of) + .toList(); + return ResponseEntity.ok(responses); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java index a190bad..6772804 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java @@ -3,10 +3,15 @@ import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.problem.service.ProblemDeleteService; +import com.moplus.moplus_server.domain.problem.service.ProblemGetService; import com.moplus.moplus_server.domain.problem.service.ProblemSaveService; +import com.moplus.moplus_server.domain.problem.service.ProblemUpdateService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -16,9 +21,20 @@ @RestController @RequestMapping("/api/v1/problems") @RequiredArgsConstructor -public class ProblemSaveController { +public class ProblemController { private final ProblemSaveService problemSaveService; + private final ProblemUpdateService problemUpdateService; + private final ProblemGetService problemGetService; + private final ProblemDeleteService problemDeleteService; + + @GetMapping("/{id}") + @Operation(summary = "문항 조회", description = "문항를 조회합니다.") + public ResponseEntity getProblem( + @PathVariable("id") String id + ) { + return ResponseEntity.ok(problemGetService.getProblem(id)); + } @PostMapping("") @Operation(summary = "문항 생성", description = "문제를 생성합니다. 새끼 문항은 list 순서대로 sequence를 저장합니다.") @@ -29,11 +45,20 @@ public ResponseEntity createProblem( } @PostMapping("/{id}") - @Operation(summary = "문항 업데이트", description = "문제를 업데이트합니다. 새끼 문항은 수정된 리스트, 새로 생성된 리스트, 삭제된 리스트가 필요합니다.") + @Operation(summary = "문항 업데이트", description = "문제를 업데이트합니다. 문항 번호, 모의고사는 수정할 수 없습니다. 새로 추가되는 새끼문항 id는 빈 값입니다.") public ResponseEntity updateProblem( @PathVariable("id") String id, @RequestBody ProblemUpdateRequest request ) { - return ResponseEntity.ok(problemSaveService.updateProblem(id, request)); + return ResponseEntity.ok(problemUpdateService.updateProblem(id, request)); + } + + @DeleteMapping("/{id}") + @Operation(summary = "문항 삭제") + public ResponseEntity updateProblem( + @PathVariable("id") String id + ) { + problemDeleteService.deleteProblem(id); + return ResponseEntity.ok().body(null); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java index 7bbede9..2067862 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java @@ -2,7 +2,6 @@ import com.moplus.moplus_server.domain.problem.domain.Answer; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; -import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; import com.moplus.moplus_server.global.common.BaseEntity; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; @@ -11,6 +10,8 @@ import jakarta.persistence.ElementCollection; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -27,14 +28,16 @@ public class ChildProblem extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "problem_id") + @Column(name = "child_problem_id") Long id; @ElementCollection - @CollectionTable(name = "child_problem_concept", joinColumns = @JoinColumn(name = "concept_tag_id")) + @CollectionTable(name = "child_problem_concept", joinColumns = @JoinColumn(name = "child_problem_id")) + @Column(name = "concept_tag_id") Set conceptTagIds; private String imageUrl; @Embedded private Answer answer; + @Enumerated(EnumType.STRING) private ProblemType problemType; private int sequence; @@ -57,12 +60,12 @@ public void validateAnswerByType(String answer, ProblemType problemType) { } } - public void update(ChildProblemUpdateRequest request) { - this.imageUrl = request.imageUrl(); - this.problemType = request.problemType(); - this.answer = new Answer(request.answer(), request.problemType()); - this.conceptTagIds = request.conceptTagIds(); - this.sequence = request.sequence(); + public void update(ChildProblem input) { + this.imageUrl = input.imageUrl; + this.problemType = input.problemType; + this.answer = input.answer; + this.conceptTagIds = input.conceptTagIds; + this.sequence = input.sequence; } public String getAnswer() { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java index f2f064f..1782bd8 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java @@ -1,6 +1,9 @@ package com.moplus.moplus_server.domain.problem.domain.practiceTest; +import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -12,19 +15,22 @@ @Entity @Table(name = "practice_test_tag") @NoArgsConstructor -public class PracticeTest { +public class PracticeTestTag { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; + @Column(name = "test_year") private int year; + @Column(name = "test_month") private int month; + @Enumerated(value = EnumType.STRING) private Subject subject; private String area; - public PracticeTest(String name, int year, int month, Subject subject) { + public PracticeTestTag(String name, int year, int month, Subject subject) { this.name = name; this.year = year; this.month = month; diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index b72ceb8..b574ba4 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -2,12 +2,11 @@ import com.moplus.moplus_server.domain.problem.domain.Answer; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; -import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTest; -import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemPostRequest; -import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; import jakarta.persistence.Embedded; import jakarta.persistence.EmbeddedId; @@ -17,6 +16,8 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.OrderBy; import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Set; import lombok.Builder; @@ -42,11 +43,12 @@ public class Problem extends BaseEntity { String seniorTipImageUrl; String prescriptionImageUrl; @ElementCollection - @CollectionTable(name = "problem_concept", joinColumns = @JoinColumn(name = "concept_tag_id")) + @CollectionTable(name = "problem_concept", joinColumns = @JoinColumn(name = "problem_id")) + @Column(name = "concept_tag_id") Set conceptTagIds; private ProblemType problemType; private boolean isPublished; - private boolean isModified; + private boolean isVariation; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name = "problem_id") @@ -54,13 +56,12 @@ public class Problem extends BaseEntity { private List childProblems = new ArrayList<>(); @Builder - public Problem(ProblemId id, PracticeTest practiceTest, int number, String answer, String comment, + public Problem(ProblemId id, PracticeTestTag practiceTestTag, int number, String answer, String comment, String mainProblemImageUrl, String mainAnalysisImageUrl, String readingTipImageUrl, String seniorTipImageUrl, - String prescriptionImageUrl, ProblemType problemType, Set conceptTagIds, - List childProblems) { + String prescriptionImageUrl, Set conceptTagIds) { this.id = id; - this.practiceTestId = practiceTest.getId(); + this.practiceTestId = practiceTestTag.getId(); this.number = number; this.comment = comment; this.mainProblemImageUrl = mainProblemImageUrl; @@ -68,38 +69,58 @@ public Problem(ProblemId id, PracticeTest practiceTest, int number, String answe this.readingTipImageUrl = readingTipImageUrl; this.seniorTipImageUrl = seniorTipImageUrl; this.prescriptionImageUrl = prescriptionImageUrl; - this.problemType = ProblemType.getTypeForProblem(practiceTest.getSubject().getValue(), number); + this.problemType = ProblemType.getTypeForProblem(practiceTestTag.getSubject().getValue(), number); this.answer = new Answer(answer, this.problemType); - this.conceptTagIds = conceptTagIds; - this.childProblems = childProblems; + this.conceptTagIds = new HashSet<>(conceptTagIds); this.isPublished = false; - this.isModified = false; + this.isVariation = false; } public String getAnswer() { return answer.getValue(); } - public void addChildProblem(ChildProblemPostRequest request) { - ChildProblem childProblem = ChildProblem.builder() - .imageUrl(request.imageUrl()) - .problemType(request.problemType()) - .answer(request.answer()) - .conceptTagIds(request.conceptTagIds()) - .sequence(request.sequence()) - .build(); - childProblems.add(request.sequence(), childProblem); + public void addChildProblem(List inputChildProblems) { + List mutableChildProblems = new ArrayList<>(inputChildProblems); + mutableChildProblems.sort(Comparator.comparingInt(ChildProblem::getSequence)); + mutableChildProblems.forEach(childProblems::add); + mutableChildProblems.forEach(childProblem -> conceptTagIds.addAll(childProblem.getConceptTagIds())); } - public void updateChildProblem(ChildProblemUpdateRequest request) { - childProblems.get(request.sequence()).update(request); + public void update(Problem inputProblem) { + this.conceptTagIds = new HashSet<>(inputProblem.getConceptTagIds()); + this.number = inputProblem.getNumber(); + this.answer = new Answer(inputProblem.getAnswer(), this.problemType); + this.comment = inputProblem.getComment(); + this.mainProblemImageUrl = inputProblem.getMainProblemImageUrl(); + this.mainAnalysisImageUrl = inputProblem.getMainAnalysisImageUrl(); + this.readingTipImageUrl = inputProblem.getReadingTipImageUrl(); + this.seniorTipImageUrl = inputProblem.getSeniorTipImageUrl(); + this.prescriptionImageUrl = inputProblem.getPrescriptionImageUrl(); } - public void deleteChildProblem(Long childProblemId) { - childProblems.forEach(childProblem -> { - if (childProblem.getId().equals(childProblemId)) { - childProblems.remove(childProblem); - } + public void updateChildProblem(List inputChildProblems) { + List mutableChildProblems = new ArrayList<>(inputChildProblems); + mutableChildProblems.sort(Comparator.comparingInt(ChildProblem::getSequence)); + + inputChildProblems.forEach(childProblem -> { + childProblems.stream() + .filter(existingChildProblem -> existingChildProblem.getId().equals(childProblem.getId())) + .findFirst() + .ifPresentOrElse( + existingChildProblem -> { + existingChildProblem.update(childProblem); + conceptTagIds.addAll(existingChildProblem.getConceptTagIds()); + }, + () -> { + childProblems.add(childProblem); + conceptTagIds.addAll(childProblem.getConceptTagIds()); + } + ); }); } + + public void deleteChildProblem(List deleteChildProblems) { + childProblems.removeIf(childProblem -> deleteChildProblems.contains(childProblem.getId())); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java index 847b283..2bc4343 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java @@ -3,8 +3,10 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import java.io.Serializable; +import lombok.Getter; import lombok.NoArgsConstructor; +@Getter @Embeddable @NoArgsConstructor public class ProblemId implements Serializable { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java index e5daf59..f3d3e82 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java @@ -1,6 +1,6 @@ package com.moplus.moplus_server.domain.problem.domain.problem; -import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTest; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import java.util.concurrent.atomic.AtomicInteger; import lombok.RequiredArgsConstructor; @@ -15,20 +15,20 @@ public class ProblemIdService { /* 문제 ID 생성 로직 - AA : 과목 ( 1: 수학, 2: 영어, 3: 국어, 4: 사회, 5: 과학 ) - S : ( 1: 고1, 2: 고2, 3: 미적분, 4: 기하, 5: 확률과 통계, 6: 가형, 7: 나형 ) YY: 년도 (두 자리) MM: 월 (두 자리) NN : 번호 (01~99) + AA : 영역 ( 01: 수학, 02: 영어, 03: 국어, 04: 사회, 05: 과학 ) + S : ( 1: 고1, 2: 고2, 3: 미적분, 4: 기하, 5: 확률과 통계, 6: 가형, 7: 나형 ) C : 변형 여부 ( 0: 기본, 1: 변형 ) XXX : 3자리 구분 숫자 */ - public ProblemId nextId(int number, PracticeTest practiceTest) { + public ProblemId nextId(int number, PracticeTestTag practiceTestTag) { int DEFAULT_AREA = 1; //현재 영역은 수학밖에 없음 - int subject = practiceTest.getSubject().getIdCode(); // AA (과목) - int year = practiceTest.getYear() % 100; // YY (두 자리 연도) - int month = practiceTest.getMonth(); // MM (두 자리 월) + int subject = practiceTestTag.getSubject().getIdCode(); // AA (과목) + int year = practiceTestTag.getYear() % 100; // YY (두 자리 연도) + int month = practiceTestTag.getMonth(); // MM (두 자리 월) int DEFAULT_MODIFIED = 0; // 변형 여부 (0: 기본, 1: 변형) String generatedId; @@ -37,9 +37,9 @@ public ProblemId nextId(int number, PracticeTest practiceTest) { // 중복되지 않는 ID 찾을 때까지 반복 do { sequence = SEQUENCE.getAndIncrement() % 1000; // 000~999 순환 - generatedId = String.format("%02d%d%02d%02d%02d%d%03d", - DEFAULT_AREA, subject, year, month, - number, DEFAULT_MODIFIED, sequence); + generatedId = String.format("%02d%02d%02d%02d%d%d%03d", + year, month, number, DEFAULT_AREA, + subject, DEFAULT_MODIFIED, sequence); } while (problemRepository.existsById(new ProblemId(generatedId))); // ID가 이미 존재하면 재생성 return new ProblemId(generatedId); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java index f8c5f35..d080b17 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java @@ -1,9 +1,11 @@ package com.moplus.moplus_server.domain.problem.dto.request; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.Set; public record ChildProblemUpdateRequest( + @Schema(description = "새로 생성되는 새끼문항은 빈 값입니다.") Long id, String imageUrl, ProblemType problemType, diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java index 3af1adc..d6e5821 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java @@ -1,6 +1,6 @@ package com.moplus.moplus_server.domain.problem.dto.request; -import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTest; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import java.util.List; @@ -19,11 +19,11 @@ public record ProblemPostRequest( String prescriptionImageUrl, List childProblems ) { - public Problem toEntity(PracticeTest practiceTest, ProblemId problemId) { + public Problem toEntity(PracticeTestTag practiceTestTag, ProblemId problemId) { return Problem.builder() .id(problemId) .conceptTagIds(conceptTagIds) - .practiceTest(practiceTest) + .practiceTestTag(practiceTestTag) .number(number) .answer(answer) .comment(comment) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java index b1a462b..9e04f0d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java @@ -5,8 +5,6 @@ public record ProblemUpdateRequest( Set conceptTagIds, - Long practiceTestId, - int number, int answer, String comment, String mainProblemImageUrl, @@ -15,7 +13,6 @@ public record ProblemUpdateRequest( String seniorTipImageUrl, String prescriptionImageUrl, List updateChildProblems, - List createChildProblems, - List deleteChildProblems + List deleteChildProblems ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java index a623b73..a4b0f1c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java @@ -1,2 +1,15 @@ -package com.moplus.moplus_server.domain.problem.dto.response;public record PracticeTestTagResponse() { +package com.moplus.moplus_server.domain.problem.dto.response; + +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; + +public record PracticeTestTagResponse( + Long id, + String name +) { + public static PracticeTestTagResponse of(PracticeTestTag practiceTestTag) { + return new PracticeTestTagResponse( + practiceTestTag.getId(), + practiceTestTag.getName() + ); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java index be781ed..9b28686 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ChildProblemRepository.java @@ -1,7 +1,13 @@ package com.moplus.moplus_server.domain.problem.repository; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; public interface ChildProblemRepository extends JpaRepository { + + default ChildProblem findByIdElseThrow(Long childProblemId) { + return findById(childProblemId).orElseThrow(() -> new NotFoundException(ErrorCode.CHILD_PROBLEM_NOT_FOUND)); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/PracticeTestTagRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/PracticeTestTagRepository.java index 9e5a514..2e02d64 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/PracticeTestTagRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/PracticeTestTagRepository.java @@ -1,13 +1,13 @@ package com.moplus.moplus_server.domain.problem.repository; -import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTest; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; -public interface PracticeTestTagRepository extends JpaRepository { +public interface PracticeTestTagRepository extends JpaRepository { - default PracticeTest findByIdElseThrow(Long id) { + default PracticeTestTag findByIdElseThrow(Long id) { return findById(id) .orElseThrow(() -> new NotFoundException(ErrorCode.PRACTICE_TEST_NOT_FOUND)); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java index 99d0d05..494f0c9 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java @@ -17,6 +17,13 @@ default void existsByPracticeTestIdAndNumberOrThrow(Long practiceTestId, int num } } + default void existsByIdElseThrow(ProblemId problemId) { + if (!existsById(problemId)) { + throw new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND); + } + } + + default Problem findByIdElseThrow(ProblemId problemId) { return findById(problemId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java index 3d465ab..1bb193f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java @@ -1,2 +1,20 @@ -package com.moplus.moplus_server.domain.problem.service;public class ProblemDeleteService { +package com.moplus.moplus_server.domain.problem.service; + +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProblemDeleteService { + + private final ProblemRepository problemRepository; + + @Transactional + public void deleteProblem(String problemId) { + problemRepository.existsByIdElseThrow(new ProblemId(problemId)); + problemRepository.deleteById(new ProblemId(problemId)); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java index 4182002..6db9ddc 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java @@ -1,16 +1,17 @@ package com.moplus.moplus_server.domain.problem.service; import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; -import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTest; +import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemIdService; -import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemDeleteRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; -import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; -import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.domain.problem.service.mapper.ChildProblemMapper; +import com.moplus.moplus_server.domain.problem.service.mapper.ProblemMapper; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,35 +24,23 @@ public class ProblemSaveService { private final PracticeTestTagRepository practiceTestRepository; private final ConceptTagRepository conceptTagRepository; private final ProblemIdService problemIdService; + private final ProblemMapper problemMapper; + private final ChildProblemMapper childProblemMapper; @Transactional public ProblemId createProblem(ProblemPostRequest request) { - PracticeTest practiceTest = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); - problemRepository.existsByPracticeTestIdAndNumberOrThrow(practiceTest.getId(), request.number()); + PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); + problemRepository.existsByPracticeTestIdAndNumberOrThrow(practiceTestTag.getId(), request.number()); conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); - ProblemId problemId = problemIdService.nextId(request.number(), practiceTest); - Problem problem = request.toEntity(practiceTest, problemId); - request.childProblems() - .forEach(problem::addChildProblem); + ProblemId problemId = problemIdService.nextId(request.number(), practiceTestTag); + Problem problem = problemMapper.from(request, problemId, practiceTestTag); - return problemRepository.save(problem).getId(); - } + List childProblems = request.childProblems().stream() + .map(childProblemMapper::from) + .toList(); + problem.addChildProblem(childProblems); - @Transactional - public ProblemGetResponse updateProblem(String problemId, ProblemUpdateRequest request) { - PracticeTest practiceTest = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); - problemRepository.existsByPracticeTestIdAndNumberOrThrow(practiceTest.getId(), request.number()); - conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); - - Problem problem = problemRepository.findByIdElseThrow(new ProblemId(problemId)); - request.deleteChildProblems().stream() - .map(ChildProblemDeleteRequest::childProblemId) - .forEach(problem::deleteChildProblem); - request.createChildProblems().forEach(problem::addChildProblem); - request.updateChildProblems().forEach(problem::updateChildProblem); - - Problem savedProblem = problemRepository.save(problem); - return ProblemGetResponse.of(savedProblem); + return problemRepository.save(problem).getId(); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java index fc078cc..a7f0678 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java @@ -1,2 +1,61 @@ -package com.moplus.moplus_server.domain.problem.service;public class ProblemUpdateService { +package com.moplus.moplus_server.domain.problem.service; + +import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; +import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; +import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.problem.repository.ChildProblemRepository; +import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.domain.problem.service.mapper.ChildProblemMapper; +import com.moplus.moplus_server.domain.problem.service.mapper.ProblemMapper; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProblemUpdateService { + + private final ProblemRepository problemRepository; + private final PracticeTestTagRepository practiceTestRepository; + private final ConceptTagRepository conceptTagRepository; + private final ChildProblemRepository childProblemRepository; + private final ChildProblemMapper childProblemMapper; + private final ProblemMapper problemMapper; + + @Transactional + public ProblemGetResponse updateProblem(String problemId, ProblemUpdateRequest request) { + conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); + Problem problem = problemRepository.findByIdElseThrow(new ProblemId(problemId)); + PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(problem.getPracticeTestId()); + Problem inputProblem = problemMapper.from(request, problem.getId(), practiceTestTag); + problem.update(inputProblem); + problem.deleteChildProblem(request.deleteChildProblems()); + + List childProblems = changeToChildProblems(request); + problem.updateChildProblem(childProblems); + + return ProblemGetResponse.of(problemRepository.save(problem)); + } + + private List changeToChildProblems(ProblemUpdateRequest request) { + return request.updateChildProblems().stream() + .map(this::getChildProblem) + .toList(); + } + + private ChildProblem getChildProblem(ChildProblemUpdateRequest updateChildProblem) { + if (updateChildProblem.id() == null) { + return childProblemMapper.from(updateChildProblem); + } + ChildProblem childProblem = childProblemRepository.findByIdElseThrow(updateChildProblem.id()); + childProblem.update(childProblemMapper.from(updateChildProblem)); + return childProblem; + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java index 35da321..d8a6566 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.problem.service; +package com.moplus.moplus_server.domain.problem.service.mapper; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemPostRequest; diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java index 882c71f..f1244a2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java @@ -1,4 +1,4 @@ -package com.moplus.moplus_server.domain.problem.service; +package com.moplus.moplus_server.domain.problem.service.mapper; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java index 92be481..a206e63 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/domain/PracticeTest.java @@ -10,6 +10,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.time.Duration; import lombok.Builder; import lombok.Getter; @@ -17,6 +18,7 @@ @Getter @Entity +@Table(name = "practice_test") @NoArgsConstructor public class PracticeTest extends BaseEntity { @@ -32,7 +34,6 @@ public class PracticeTest extends BaseEntity { private long viewCount = 0L; private int solvesCount = 0; private int publicationYear; - private int month = 0; @Enumerated(EnumType.STRING) private Subject subject; @@ -41,15 +42,13 @@ public class PracticeTest extends BaseEntity { @Builder public PracticeTest(String name, String round, String provider, long viewCount, int solvesCount, - int publicationYear, - int month, Subject subject, Duration averageSolvingTime) { + int publicationYear, Subject subject, Duration averageSolvingTime) { this.name = name; this.round = round; this.provider = provider; this.viewCount = viewCount; this.solvesCount = solvesCount; this.publicationYear = publicationYear; - this.month = month; this.subject = subject; this.averageSolvingTime = averageSolvingTime; } diff --git a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java index fc3b4f9..e1b5fb1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/v0/practiceTest/repository/PracticeTestRepository.java @@ -12,6 +12,6 @@ public interface PracticeTestRepository extends JpaRepository findAllByOrderByViewCountDesc(); @Lock(LockModeType.PESSIMISTIC_WRITE) - @Query("select s from PracticeTest s where s.id = :id") + @Query("select s from PracticeTestTag s where s.id = :id") PracticeTest findByIdWithPessimisticLock(@Param("id") Long id); } diff --git a/src/main/java/com/moplus/moplus_server/global/common/BaseEntity.java b/src/main/java/com/moplus/moplus_server/global/common/BaseEntity.java index 82a23e2..2e0b341 100644 --- a/src/main/java/com/moplus/moplus_server/global/common/BaseEntity.java +++ b/src/main/java/com/moplus/moplus_server/global/common/BaseEntity.java @@ -4,7 +4,6 @@ import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import java.time.LocalDateTime; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @@ -26,9 +25,4 @@ public abstract class BaseEntity { @LastModifiedDate @Column(name = "update_at") private LocalDateTime updatedDate; - - @Column(name = "deleted") - @Builder.Default - private boolean deleted = false; - } diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 3968ed9..eb740fd 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -33,6 +33,9 @@ public enum ErrorCode { INVALID_MULTIPLE_CHOICE_ANSWER(HttpStatus.BAD_REQUEST, "객관식 문제의 정답은 1~5 사이의 숫자여야 합니다"), INVALID_SHORT_NUMBER_ANSWER(HttpStatus.BAD_REQUEST, "주관식 문제의 정답은 0~999 사이의 숫자여야 합니다"), + //새끼 문항 + CHILD_PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 새끼 문제를 찾을 수 없습니다"), + //개념태그 CONCEPT_TAG_NOT_FOUND_IN_LIST(HttpStatus.NOT_FOUND, "해당 리스트 중 존재하지 않는 개념 태그가 있습니다."), diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java index 30e0157..629e128 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java @@ -1,4 +1,78 @@ -import static org.junit.jupiter.api.Assertions.*; +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.Subject; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) class ProblemIdServiceTest { - + + @Mock + private ProblemRepository problemRepository; + + @InjectMocks + private ProblemIdService problemIdService; + + private PracticeTestTag practiceTestTag; + + @BeforeEach + void setUp() { + practiceTestTag = Mockito.mock(PracticeTestTag.class); + when(practiceTestTag.getSubject()).thenReturn(Subject.고2); + when(practiceTestTag.getYear()).thenReturn(2024); + when(practiceTestTag.getMonth()).thenReturn(5); + } + + @Test + void nextId_정상생성_및_중복확인() { + // given + int 문제번호 = 20; + when(problemRepository.existsById(any(ProblemId.class))).thenReturn(false); // 중복 없음 + + // when + ProblemId generatedId = problemIdService.nextId(문제번호, practiceTestTag); + + // then + assertThat(generatedId).isNotNull(); + assertThat(generatedId.getId()).matches("\\d{13}"); // ID 형식이 맞는지 확인 + assertThat(generatedId.getId()).startsWith("2405200120"); + + // 문제 ID 중복 확인을 위해 existsById 호출 확인 + verify(problemRepository, atLeastOnce()).existsById(any(ProblemId.class)); + + } + + @Test + void nextId_중복발생시_다시_생성() { + // given + int 문제번호 = 2; + when(problemRepository.existsById(any(ProblemId.class))) + .thenReturn(true) // 첫 번째 생성된 ID는 중복됨 + .thenReturn(false); // 두 번째는 중복 없음 + + // when + ProblemId generatedId = problemIdService.nextId(문제번호, practiceTestTag); + + // then + assertThat(generatedId).isNotNull(); + assertThat(generatedId.getId()).matches("\\d{13}"); + assertThat(generatedId.getId()).startsWith("2405020120"); + + // 중복된 ID가 나왔으므로 existsById가 최소 두 번 이상 호출되었는지 확인 + verify(problemRepository, atLeast(2)).existsById(any(ProblemId.class)); + } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java index 2fcce6b..65db7b9 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java @@ -1,4 +1,150 @@ -import static org.junit.jupiter.api.Assertions.*; +package com.moplus.moplus_server.domain.problem.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemPostRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import java.util.List; +import java.util.Set; +import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@ActiveProfiles("h2test") +@Sql({"/practice-test-tag.sql", "/concept-tag.sql"}) +@SpringBootTest class ProblemSaveServiceTest { - + + @Autowired + private ProblemSaveService problemSaveService; + + @Autowired + private ProblemRepository problemRepository; + + private ProblemPostRequest problemPostRequestOutOfOrder; + private ProblemPostRequest problemPostRequestInOrder; + + @BeforeEach + void setUp() { + // 🔹 1. 일부러 순서를 뒤죽박죽으로 설정한 문제 + ChildProblemPostRequest childProblem1 = new ChildProblemPostRequest( + "child1.png", ProblemType.SHORT_STRING_ANSWER, "정답1", Set.of(3L, 4L), 3 + ); + ChildProblemPostRequest childProblem2 = new ChildProblemPostRequest( + "child2.png", ProblemType.MULTIPLE_CHOICE, "1", Set.of(5L, 6L), 1 + ); + ChildProblemPostRequest childProblem3 = new ChildProblemPostRequest( + "child3.png", ProblemType.MULTIPLE_CHOICE, "2", Set.of(3L, 4L), 0 + ); + ChildProblemPostRequest childProblem4 = new ChildProblemPostRequest( + "child4.png", ProblemType.SHORT_NUMBER_ANSWER, "0", Set.of(1L, 2L), 2 + ); + + problemPostRequestOutOfOrder = new ProblemPostRequest( + Set.of(1L, 2L), + 1L, + 21, + "1", + "설명", + "mainProblem.png", + "mainAnalysis.png", + "readingTip.png", + "seniorTip.png", + "prescription.png", + List.of(childProblem1, childProblem2, childProblem3, childProblem4) // 🔹 순서 뒤죽박죽 + ); + + // 🔹 2. 순서가 올바른 상태에서 입력되는 문제 + problemPostRequestInOrder = new ProblemPostRequest( + Set.of(1L, 2L), + 1L, + 20, + "2", + "다른 설명", + "mainProblem2.png", + "mainAnalysis2.png", + "readingTip2.png", + "seniorTip2.png", + "prescription2.png", + List.of(childProblem3, childProblem2, childProblem4, childProblem1) // 🔹 순서 유지 (0,1,2,3) + ); + } + + @Test + @Rollback(value = false) + void 정상동작() { + + // when + ProblemId createdProblemId = problemSaveService.createProblem(problemPostRequestInOrder); + + // then + assertThat(createdProblemId).isNotNull(); + assertThat(createdProblemId.getId()).startsWith("2405200120"); // ID 앞부분 확인 + + Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemId); + + // 모든 자식 문제의 conceptTagIds가 부모 문제의 conceptTagIds에 포함되는지 검증 + Set problemTags = savedProblem.getConceptTagIds(); + problemPostRequestInOrder.childProblems().forEach(child -> { + assertThat(problemTags).containsAll(child.conceptTagIds()); + }); + + // 자식 문제의 순서 검증 + List childProblems = savedProblem.getChildProblems(); + + assertThat(childProblems).hasSize(4); // 자식 문제 개수 검증 + + // 저장된 자식 문제가 원래 요청한 `sequence` 순서와 같은지 확인 + IntStream.range(0, childProblems.size()).forEach(i -> { + assertThat(childProblems.get(i).getSequence()).isEqualTo(i); + }); + } + + @Test + @Rollback(true) + void 자식문제_올바른_순서_저장() { + // when + ProblemId createdProblemId = problemSaveService.createProblem(problemPostRequestOutOfOrder); + + // then + assertThat(createdProblemId).isNotNull(); + assertThat(createdProblemId.getId()).startsWith("2405210120"); // ID 앞부분 확인 + + // 저장된 문제 조회 + Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemId); + + // ✅ 모든 자식 문제의 conceptTagIds가 부모 문제의 conceptTagIds에 포함되는지 검증 + Set problemTags = savedProblem.getConceptTagIds(); + problemPostRequestOutOfOrder.childProblems().forEach(child -> { + assertThat(problemTags).containsAll(child.conceptTagIds()); + }); + + // ✅ 자식 문제의 순서 검증 + List childProblems = savedProblem.getChildProblems(); + + assertThat(childProblems).hasSize(4); // 자식 문제 개수 검증 + + // 🔹 저장된 자식 문제들이 `sequence` 오름차순으로 정렬되었는지 확인 + IntStream.range(0, childProblems.size()).forEach(i -> { + assertThat(childProblems.get(i).getSequence()).isEqualTo(i); + }); + + // 🔹 정렬 후 올바른 문제인지 검증 + assertThat(childProblems.get(0).getImageUrl()).isEqualTo("child3.png"); // sequence 0 + assertThat(childProblems.get(1).getImageUrl()).isEqualTo("child2.png"); // sequence 1 + assertThat(childProblems.get(2).getImageUrl()).isEqualTo("child4.png"); // sequence 2 + assertThat(childProblems.get(3).getImageUrl()).isEqualTo("child1.png"); // sequence 3 + } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java index 3231e35..6d59ad5 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java @@ -1,4 +1,112 @@ -import static org.junit.jupiter.api.Assertions.*; +package com.moplus.moplus_server.domain.problem.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; +import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import java.util.List; +import java.util.Set; +import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@ActiveProfiles("h2test") +@Sql({"/practice-test-tag.sql", "/concept-tag.sql", "/insert-problem.sql"}) +@SpringBootTest class ProblemUpdateServiceTest { - + + @Autowired + private ProblemUpdateService problemUpdateService; + + @Autowired + private ProblemRepository problemRepository; + + private ProblemId problemId; + private ProblemUpdateRequest problemUpdateRequest; + + @BeforeEach + void setUp() { + problemId = new ProblemId("240520012001"); + + // 🔹 새 자식 문제 추가 + ChildProblemUpdateRequest newChildProblem = new ChildProblemUpdateRequest( + null, + "newChild.png", + ProblemType.SHORT_STRING_ANSWER, + "새로운 정답", + Set.of(1L, 2L), + 1 + ); + + // 🔹 기존 자식 문제 업데이트 + ChildProblemUpdateRequest updateChildProblem = new ChildProblemUpdateRequest( + 1L, // 기존 자식 문제 ID + "updatedChild.png", + ProblemType.MULTIPLE_CHOICE, + "2", + Set.of(2L, 3L), + 0 + ); + + // 🔹 기존 자식 문제 삭제 + List deleteChildProblem = List.of(2L); // 삭제할 자식 문제 ID + + problemUpdateRequest = new ProblemUpdateRequest( + Set.of(1L, 2L, 3L), // 업데이트할 부모 문제의 Concept Tags + 1, // 문제 정답 + "수정된 설명", // 새로운 설명 + "updatedMainProblem.png", + "updatedMainAnalysis.png", + "updatedReadingTip.png", + "updatedSeniorTip.png", + "updatedPrescription.png", + List.of(newChildProblem, updateChildProblem), // 업데이트할 자식 문제 + deleteChildProblem // 삭제할 자식 문제 + ); + } + + @Test + void 문제_업데이트_정상동작() { + // when + ProblemGetResponse response = problemUpdateService.updateProblem(problemId.getId(), + problemUpdateRequest); + + // then + assertThat(response).isNotNull(); + assertThat(response.comment()).isEqualTo("수정된 설명"); // ✅ 설명이 변경되었는지 검증 + assertThat(response.mainProblemImageUrl()).isEqualTo("updatedMainProblem.png"); // ✅ 이미지 URL 변경 확인 + + Problem updatedProblem = problemRepository.findByIdElseThrow(problemId); + + // ✅ 자식 문제 개수 검증 + List childProblems = updatedProblem.getChildProblems(); + assertThat(childProblems).hasSize(2); // 기존 2개 → 1개 삭제, 1개 추가 후 2개 + + // ✅ 부모 문제의 conceptTagIds가 자식 문제의 conceptTagIds를 모두 포함하는지 검증 + Set problemTags = updatedProblem.getConceptTagIds(); + updatedProblem.getChildProblems().forEach(child -> { + assertThat(problemTags).containsAll(child.getConceptTagIds()); + }); + + // ✅ 자식 문제 순서가 올바르게 정렬되었는지 확인 + IntStream.range(0, childProblems.size()).forEach(i -> { + assertThat(childProblems.get(i).getSequence()).isEqualTo(i); + }); + + // ✅ 개별 자식 문제 검증 + assertThat(childProblems.get(0).getImageUrl()).isEqualTo("updatedChild.png"); // 기존 자식 문제 업데이트 확인 + assertThat(childProblems.get(1).getImageUrl()).isEqualTo("newChild.png"); // 새 자식 문제 추가 확인 + } } \ No newline at end of file diff --git a/src/test/resources/auth-test-data.sql b/src/test/resources/auth-test-data.sql index 595162a..cb7319e 100644 --- a/src/test/resources/auth-test-data.sql +++ b/src/test/resources/auth-test-data.sql @@ -1,4 +1,4 @@ -INSERT INTO member (deleted, created_at, update_at, member_id, email, password, name, role) -VALUES (false, '2024-07-24 21:27:20.000000', '2024-07-24 21:27:21.000000', 1, 'admin@example.com', +INSERT INTO member (created_at, update_at, member_id, email, password, name, role) +VALUES ('2024-07-24 21:27:20.000000', '2024-07-24 21:27:21.000000', 1, 'admin@example.com', 'password123', '홍길동', 'ADMIN'); diff --git a/src/test/resources/concept-tag.sql b/src/test/resources/concept-tag.sql index e69de29..6b94678 100644 --- a/src/test/resources/concept-tag.sql +++ b/src/test/resources/concept-tag.sql @@ -0,0 +1,12 @@ +INSERT INTO concept_tag (name) +VALUES ('미분 개념'); +INSERT INTO concept_tag (name) +VALUES ('적분 개념'); +INSERT INTO concept_tag (name) +VALUES ('삼각함수 개념'); +INSERT INTO concept_tag (name) +VALUES ('행렬 개념'); +INSERT INTO concept_tag (name) +VALUES ('확률과 통계 개념'); +INSERT INTO concept_tag (name) +VALUES ('기하 개념'); \ No newline at end of file diff --git a/src/test/resources/insert-problem.sql b/src/test/resources/insert-problem.sql index e69de29..9f81e33 100644 --- a/src/test/resources/insert-problem.sql +++ b/src/test/resources/insert-problem.sql @@ -0,0 +1,29 @@ +DELETE +FROM child_problem_concept; +DELETE +FROM child_problem; + +INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, + main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, + is_published, is_variation) +VALUES ('240520012001', 1, 1, '1', '기존 문제 설명', + 'mainProblem.png', 'mainAnalysis.png', 'readingTip.png', 'seniorTip.png', 'prescription.png', + false, false); + +-- ✅ 기존 자식 문제(ChildProblem) 삽입 +INSERT INTO child_problem (child_problem_id, problem_id, image_url, problem_type, answer, sequence) +VALUES (1, '240520012001', 'child1.png', 'MULTIPLE_CHOICE', '1', 0), + (2, '240520012001', 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 0); + +-- ✅ 문제-컨셉 태그 연결 (기존 문제의 ConceptTag) +INSERT INTO problem_concept (problem_id, concept_tag_id) +VALUES ('240520012001', 1), + ('240520012001', 2), + ('240520012001', 3); + +-- ✅ 자식 문제-컨셉 태그 연결 +INSERT INTO child_problem_concept (child_problem_id, concept_tag_id) +VALUES (1, 3), + (1, 4), + (2, 5), + (2, 6); \ No newline at end of file From 22c15cf4872ba7dc4161dc7f2126ec1ae52bd5b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 30 Jan 2025 22:13:22 +0900 Subject: [PATCH 030/115] =?UTF-8?q?[fix/#23]=20updateChildProblem=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=95=84=EC=9A=94=EC=97=86=EB=8A=94=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moplus_server/domain/problem/domain/problem/Problem.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index b574ba4..b059bf3 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -100,11 +100,8 @@ public void update(Problem inputProblem) { } public void updateChildProblem(List inputChildProblems) { - List mutableChildProblems = new ArrayList<>(inputChildProblems); - mutableChildProblems.sort(Comparator.comparingInt(ChildProblem::getSequence)); - inputChildProblems.forEach(childProblem -> { - childProblems.stream() + this.childProblems.stream() .filter(existingChildProblem -> existingChildProblem.getId().equals(childProblem.getId())) .findFirst() .ifPresentOrElse( From c389d797b39b171585f9051c9da07849f720e8de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 30 Jan 2025 22:45:04 +0900 Subject: [PATCH 031/115] =?UTF-8?q?[feature/#23]=20=EB=AA=A8=EC=9D=98?= =?UTF-8?q?=EA=B3=A0=EC=82=AC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?swagger=20=EC=84=A4=EB=AA=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problem/controller/PracticeTestTagController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java index 443b7bc..1534257 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java @@ -2,6 +2,7 @@ import com.moplus.moplus_server.domain.problem.dto.response.PracticeTestTagResponse; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; +import io.swagger.v3.oas.annotations.Operation; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -17,6 +18,7 @@ public class PracticeTestTagController { private final PracticeTestTagRepository practiceTestTagRepository; @GetMapping("") + @Operation(summary = "모의고사 목록 조회") public ResponseEntity> getPracticeTestTags() { List responses = practiceTestTagRepository.findAll().stream() .map(PracticeTestTagResponse::of) From 000f92a16a0f6c940719de3aa8da62fe2417c52e Mon Sep 17 00:00:00 2001 From: HongGit Date: Sat, 1 Feb 2025 01:00:33 +0900 Subject: [PATCH 032/115] =?UTF-8?q?[feat/#29]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EB=A3=A8=ED=8A=B8=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problemset/domain/ProblemSet.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java new file mode 100644 index 0000000..e7cc222 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -0,0 +1,46 @@ +package com.moplus.moplus_server.domain.problemset.domain; + +import com.moplus.moplus_server.global.common.BaseEntity; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import java.util.HashSet; +import java.util.Set; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ProblemSet extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "problem_set_id") + Long id; + + private String name; + private boolean isDeleted; + private boolean isConfirmed; + + @ElementCollection + @CollectionTable(name = "problem_set_problems", joinColumns = @JoinColumn(name = "problem_set_id")) + @Column(name = "problem_id") + private Set problemIds = new HashSet<>(); + + @Builder + public ProblemSet(String name) { + this.name = name; + this.isDeleted = false; + this.isConfirmed = false; + } + +} From b75932c2490201376e7b8a497e8a41f8720c82d0 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sat, 1 Feb 2025 01:00:40 +0900 Subject: [PATCH 033/115] =?UTF-8?q?[feat/#29]=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/publish/domain/Publish.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java b/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java new file mode 100644 index 0000000..55a66ab --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.publish.domain; + +import com.moplus.moplus_server.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Publish extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "publish_id") + private Long id; + + @Column(nullable = false) + private LocalDate publishedDate; + + @Column(name = "problem_set_id", nullable = false) + private Long problemSetId; + + @Builder + public Publish(LocalDate publishedDate, Long problemSetId) { + this.publishedDate = publishedDate; + this.problemSetId = problemSetId; + } + +} From 6c7a70404cd64534a15f630125fecd020bea07a8 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sat, 1 Feb 2025 01:10:31 +0900 Subject: [PATCH 034/115] =?UTF-8?q?[fix/#29]=20ProblemId=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moplus_server/domain/problemset/domain/ProblemSet.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index e7cc222..64dd964 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -1,5 +1,6 @@ package com.moplus.moplus_server.domain.problemset.domain; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; @@ -34,7 +35,7 @@ public class ProblemSet extends BaseEntity { @ElementCollection @CollectionTable(name = "problem_set_problems", joinColumns = @JoinColumn(name = "problem_set_id")) @Column(name = "problem_id") - private Set problemIds = new HashSet<>(); + private Set problemIds = new HashSet<>(); @Builder public ProblemSet(String name) { From 8795115020c877536c8f49d620041ce43b959f0b Mon Sep 17 00:00:00 2001 From: HongGit Date: Sat, 1 Feb 2025 03:04:33 +0900 Subject: [PATCH 035/115] =?UTF-8?q?[feat/#31]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EB=B3=B4=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problemset/domain/ProblemSet.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index 64dd964..f580d71 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -5,14 +5,14 @@ import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; -import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; -import java.util.HashSet; -import java.util.Set; +import jakarta.persistence.OrderColumn; +import java.util.ArrayList; +import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -35,7 +35,8 @@ public class ProblemSet extends BaseEntity { @ElementCollection @CollectionTable(name = "problem_set_problems", joinColumns = @JoinColumn(name = "problem_set_id")) @Column(name = "problem_id") - private Set problemIds = new HashSet<>(); + @OrderColumn(name = "sequence") + private List problemIds = new ArrayList<>(); @Builder public ProblemSet(String name) { @@ -44,4 +45,8 @@ public ProblemSet(String name) { this.isConfirmed = false; } + public void updateProblemOrder(List newProblems) { + this.problemIds.clear(); + this.problemIds.addAll(newProblems); + } } From 42953409be3129508f05d2258dc1b40d2146a191 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sat, 1 Feb 2025 03:05:06 +0900 Subject: [PATCH 036/115] =?UTF-8?q?[feat/#31]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=83=9D=EC=84=B1=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProblemSetController.java | 27 ++++++++++++++ .../dto/request/ProblemSetPostRequest.java | 15 ++++++++ .../repository/ProblemSetRepository.java | 8 +++++ .../service/ProblemSetSaveService.java | 35 +++++++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java new file mode 100644 index 0000000..09b4752 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -0,0 +1,27 @@ +package com.moplus.moplus_server.domain.problemset.controller; + +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; +import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/problemSet") +@RequiredArgsConstructor +public class ProblemSetController { + + private final ProblemSetSaveService problemSetSaveService; + + @PostMapping("") + @Operation(summary = "문항세트 생성", description = "세트를 생성합니다. 문항은 요청 순서대로 저장합니다.") + public ResponseEntity createProblemSet( + @RequestBody ProblemSetPostRequest request + ) { + return ResponseEntity.ok(problemSetSaveService.createProblemSet(request)); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java new file mode 100644 index 0000000..01ff421 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java @@ -0,0 +1,15 @@ +package com.moplus.moplus_server.domain.problemset.dto.request; + +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import java.util.List; + +public record ProblemSetPostRequest( + String problemSetName, + List problems +) { + public ProblemSet toEntity(String problemSetName) { + return ProblemSet.builder() + .name(problemSetName) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java new file mode 100644 index 0000000..4027a07 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java @@ -0,0 +1,8 @@ +package com.moplus.moplus_server.domain.problemset.repository; + +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProblemSetRepository extends JpaRepository { + +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java new file mode 100644 index 0000000..054a0e0 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -0,0 +1,35 @@ +package com.moplus.moplus_server.domain.problemset.service; + +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProblemSetSaveService { + + private final ProblemSetRepository problemSetRepository; + private final ProblemRepository problemRepository; + + @Transactional + public Long createProblemSet(ProblemSetPostRequest request) { + // 문제 ID 리스트를 ProblemId 객체로 변환 + List problemIdList = request.problems().stream() + .map(ProblemId::new) + .toList(); + + // 모든 문항이 DB에 존재하는지 검증 + problemIdList.forEach(problemRepository::findByIdElseThrow); + + // ProblemSet 생성 + ProblemSet problemSet = request.toEntity(request.problemSetName()); + + return problemSetRepository.save(problemSet).getId(); + } +} From 979177b250afbae70a6f9f6b4234507a344ccb94 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sat, 1 Feb 2025 03:18:06 +0900 Subject: [PATCH 037/115] =?UTF-8?q?[feat/#31]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EB=AC=B8=ED=95=AD=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProblemSetController.java | 14 +++++++++++++- .../dto/request/ProblemReorderRequest.java | 8 ++++++++ .../repository/ProblemSetRepository.java | 6 ++++++ .../problemset/service/ProblemSetSaveService.java | 14 ++++++++++++++ .../global/error/exception/ErrorCode.java | 3 +++ 5 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java index 09b4752..5422a98 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -1,11 +1,14 @@ package com.moplus.moplus_server.domain.problemset.controller; +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -18,10 +21,19 @@ public class ProblemSetController { private final ProblemSetSaveService problemSetSaveService; @PostMapping("") - @Operation(summary = "문항세트 생성", description = "세트를 생성합니다. 문항은 요청 순서대로 저장합니다.") + @Operation(summary = "문항세트 생성", description = "문항세트를 생성합니다. 문항은 요청 순서대로 저장합니다.") public ResponseEntity createProblemSet( @RequestBody ProblemSetPostRequest request ) { return ResponseEntity.ok(problemSetSaveService.createProblemSet(request)); } + + @PutMapping("/{problemSetId}/sequence") + @Operation(summary = "세트 문항순서 변경", description = "문항세트 내의 문항 리스트의 순서를 변경합니다.") + public ResponseEntity reorderProblems( + @PathVariable Long problemSetId, + @RequestBody ProblemReorderRequest problemReorderRequest) { + problemSetSaveService.reorderProblems(problemSetId, problemReorderRequest); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java new file mode 100644 index 0000000..602b249 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java @@ -0,0 +1,8 @@ +package com.moplus.moplus_server.domain.problemset.dto.request; + +import java.util.List; + +public record ProblemReorderRequest( + List newProblems +) { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java index 4027a07..fff031f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java @@ -1,8 +1,14 @@ package com.moplus.moplus_server.domain.problemset.repository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; public interface ProblemSetRepository extends JpaRepository { + default ProblemSet findByIdElseThrow(Long problemSetId) { + return findById(problemSetId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_SET_NOT_FOUND)); + } + } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java index 054a0e0..365cce6 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -3,9 +3,11 @@ import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,4 +34,16 @@ public Long createProblemSet(ProblemSetPostRequest request) { return problemSetRepository.save(problemSet).getId(); } + + @Transactional + public void reorderProblems(Long problemSetId, ProblemReorderRequest problemReorderRequest) { + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + + // 기존 문항 ID 리스트 업데이트 (순서 반영) + List updatedProblemIds = problemReorderRequest.newProblems().stream() + .map(ProblemId::new) + .collect(Collectors.toList()); + + problemSet.updateProblemOrder(updatedProblemIds); + } } diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index eb740fd..1e539bf 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -52,6 +52,9 @@ public enum ErrorCode { //회원 MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 회원을 찾을 수 없습니다"), + + //문항세트 + PROBLEM_SET_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 문항세트를 찾을 수 없습니다"), ; From 0736230b665f22046d0e33ea8ed6610931965559 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sat, 1 Feb 2025 03:32:53 +0900 Subject: [PATCH 038/115] =?UTF-8?q?[feat/#31]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=88=98=EC=A0=95,=20=EC=82=AD=EC=A0=9C,?= =?UTF-8?q?=20=EC=BB=A8=ED=8E=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProblemSetController.java | 32 ++++++++++++++++-- .../domain/problemset/domain/ProblemSet.java | 13 ++++++++ .../dto/request/ProblemSetUpdateRequest.java | 9 +++++ .../service/ProblemSetSaveService.java | 33 +++++++++++++++++-- 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java index 5422a98..3874977 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -2,10 +2,12 @@ import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -32,8 +34,34 @@ public ResponseEntity createProblemSet( @Operation(summary = "세트 문항순서 변경", description = "문항세트 내의 문항 리스트의 순서를 변경합니다.") public ResponseEntity reorderProblems( @PathVariable Long problemSetId, - @RequestBody ProblemReorderRequest problemReorderRequest) { - problemSetSaveService.reorderProblems(problemSetId, problemReorderRequest); + @RequestBody ProblemReorderRequest request) { + problemSetSaveService.reorderProblems(problemSetId, request); return ResponseEntity.noContent().build(); } + + @PutMapping("/{problemSetId}") + @Operation(summary = "문항세트 수정", description = "문항세트의 이름 및 문항 리스트를 수정합니다.") + public ResponseEntity updateProblemSet( + @PathVariable Long problemSetId, + @RequestBody ProblemSetUpdateRequest request + ) { + problemSetSaveService.updateProblemSet(problemSetId, request); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/{problemSetId}") + @Operation(summary = "문항세트 삭제", description = "문항세트를 삭제합니다. (soft delete)") + public ResponseEntity deleteProblemSet( + @PathVariable Long problemSetId + ) { + problemSetSaveService.deleteProblemSet(problemSetId); + return ResponseEntity.noContent().build(); + } + + @PutMapping("/{problemSetId}/confirm") + @Operation(summary = "문항세트 컨펌 토글", description = "문항세트의 컨펌 상태를 토글합니다.") + public ResponseEntity toggleConfirmProblemSet(@PathVariable Long problemSetId) { + boolean updatedState = problemSetSaveService.toggleConfirmProblemSet(problemSetId); + return ResponseEntity.ok(updatedState); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index f580d71..d45cc27 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -49,4 +49,17 @@ public void updateProblemOrder(List newProblems) { this.problemIds.clear(); this.problemIds.addAll(newProblems); } + + public void deleteProblemSet() { + this.isDeleted = true; + } + + public void toggleConfirm(boolean isConfirmed) { + this.isConfirmed = isConfirmed; + } + + public void updateProblemSet(String name, List newProblems) { + this.name = name; + this.problemIds = newProblems; + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java new file mode 100644 index 0000000..a0e4745 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java @@ -0,0 +1,9 @@ +package com.moplus.moplus_server.domain.problemset.dto.request; + +import java.util.List; + +public record ProblemSetUpdateRequest( + String problemSetName, + List problems +) { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java index 365cce6..7aadac4 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -5,6 +5,7 @@ import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; import java.util.List; import java.util.stream.Collectors; @@ -36,14 +37,42 @@ public Long createProblemSet(ProblemSetPostRequest request) { } @Transactional - public void reorderProblems(Long problemSetId, ProblemReorderRequest problemReorderRequest) { + public void reorderProblems(Long problemSetId, ProblemReorderRequest request) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); // 기존 문항 ID 리스트 업데이트 (순서 반영) - List updatedProblemIds = problemReorderRequest.newProblems().stream() + List updatedProblemIds = request.newProblems().stream() .map(ProblemId::new) .collect(Collectors.toList()); problemSet.updateProblemOrder(updatedProblemIds); } + + @Transactional + public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) { + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + + // 문항 리스트 검증 + List problemIdList = request.problems().stream() + .map(ProblemId::new) + .collect(Collectors.toList()); + problemIdList.forEach(problemRepository::findByIdElseThrow); + + problemSet.updateProblemSet(request.problemSetName(), problemIdList); + } + + @Transactional + public void deleteProblemSet(Long problemSetId) { + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + problemSet.deleteProblemSet(); + } + + @Transactional + public boolean toggleConfirmProblemSet(Long problemSetId) { + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + + // 현재 상태 반전 (true → false, false → true) + problemSet.toggleConfirm(!problemSet.isConfirmed()); + return problemSet.isConfirmed(); + } } From 182e355aabc361c25d871ed71aae283d8cc19308 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sat, 1 Feb 2025 13:11:16 +0900 Subject: [PATCH 039/115] =?UTF-8?q?[feat/#31]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProblemSetController.java | 12 ++-- .../service/ProblemSetDeleteService.java | 21 +++++++ .../service/ProblemSetSaveService.java | 40 -------------- .../service/ProblemSetUpdateService.java | 55 +++++++++++++++++++ 4 files changed, 84 insertions(+), 44 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetDeleteService.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java index 3874977..2af7db8 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -3,7 +3,9 @@ import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; +import com.moplus.moplus_server.domain.problemset.service.ProblemSetDeleteService; import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService; +import com.moplus.moplus_server.domain.problemset.service.ProblemSetUpdateService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -21,6 +23,8 @@ public class ProblemSetController { private final ProblemSetSaveService problemSetSaveService; + private final ProblemSetUpdateService problemSetUpdateService; + private final ProblemSetDeleteService problemSetDeleteService; @PostMapping("") @Operation(summary = "문항세트 생성", description = "문항세트를 생성합니다. 문항은 요청 순서대로 저장합니다.") @@ -35,7 +39,7 @@ public ResponseEntity createProblemSet( public ResponseEntity reorderProblems( @PathVariable Long problemSetId, @RequestBody ProblemReorderRequest request) { - problemSetSaveService.reorderProblems(problemSetId, request); + problemSetUpdateService.reorderProblems(problemSetId, request); return ResponseEntity.noContent().build(); } @@ -45,7 +49,7 @@ public ResponseEntity updateProblemSet( @PathVariable Long problemSetId, @RequestBody ProblemSetUpdateRequest request ) { - problemSetSaveService.updateProblemSet(problemSetId, request); + problemSetUpdateService.updateProblemSet(problemSetId, request); return ResponseEntity.noContent().build(); } @@ -54,14 +58,14 @@ public ResponseEntity updateProblemSet( public ResponseEntity deleteProblemSet( @PathVariable Long problemSetId ) { - problemSetSaveService.deleteProblemSet(problemSetId); + problemSetDeleteService.deleteProblemSet(problemSetId); return ResponseEntity.noContent().build(); } @PutMapping("/{problemSetId}/confirm") @Operation(summary = "문항세트 컨펌 토글", description = "문항세트의 컨펌 상태를 토글합니다.") public ResponseEntity toggleConfirmProblemSet(@PathVariable Long problemSetId) { - boolean updatedState = problemSetSaveService.toggleConfirmProblemSet(problemSetId); + boolean updatedState = problemSetUpdateService.toggleConfirmProblemSet(problemSetId); return ResponseEntity.ok(updatedState); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetDeleteService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetDeleteService.java new file mode 100644 index 0000000..830d751 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetDeleteService.java @@ -0,0 +1,21 @@ +package com.moplus.moplus_server.domain.problemset.service; + +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProblemSetDeleteService { + + private final ProblemSetRepository problemSetRepository; + + @Transactional + public void deleteProblemSet(Long problemSetId) { + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + problemSet.deleteProblemSet(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java index 7aadac4..10063ac 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -35,44 +35,4 @@ public Long createProblemSet(ProblemSetPostRequest request) { return problemSetRepository.save(problemSet).getId(); } - - @Transactional - public void reorderProblems(Long problemSetId, ProblemReorderRequest request) { - ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); - - // 기존 문항 ID 리스트 업데이트 (순서 반영) - List updatedProblemIds = request.newProblems().stream() - .map(ProblemId::new) - .collect(Collectors.toList()); - - problemSet.updateProblemOrder(updatedProblemIds); - } - - @Transactional - public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) { - ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); - - // 문항 리스트 검증 - List problemIdList = request.problems().stream() - .map(ProblemId::new) - .collect(Collectors.toList()); - problemIdList.forEach(problemRepository::findByIdElseThrow); - - problemSet.updateProblemSet(request.problemSetName(), problemIdList); - } - - @Transactional - public void deleteProblemSet(Long problemSetId) { - ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); - problemSet.deleteProblemSet(); - } - - @Transactional - public boolean toggleConfirmProblemSet(Long problemSetId) { - ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); - - // 현재 상태 반전 (true → false, false → true) - problemSet.toggleConfirm(!problemSet.isConfirmed()); - return problemSet.isConfirmed(); - } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java new file mode 100644 index 0000000..8b46030 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java @@ -0,0 +1,55 @@ +package com.moplus.moplus_server.domain.problemset.service; + +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProblemSetUpdateService { + + private final ProblemSetRepository problemSetRepository; + private final ProblemRepository problemRepository; + + @Transactional + public void reorderProblems(Long problemSetId, ProblemReorderRequest request) { + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + + // 기존 문항 ID 리스트 업데이트 (순서 반영) + List updatedProblemIds = request.newProblems().stream() + .map(ProblemId::new) + .collect(Collectors.toList()); + + problemSet.updateProblemOrder(updatedProblemIds); + } + + @Transactional + public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) { + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + + // 문항 리스트 검증 + List problemIdList = request.problems().stream() + .map(ProblemId::new) + .collect(Collectors.toList()); + problemIdList.forEach(problemRepository::findByIdElseThrow); + + problemSet.updateProblemSet(request.problemSetName(), problemIdList); + } + + @Transactional + public boolean toggleConfirmProblemSet(Long problemSetId) { + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + + // 현재 상태 반전 (true → false, false → true) + problemSet.toggleConfirm(!problemSet.isConfirmed()); + return problemSet.isConfirmed(); + } +} From 8b2fdfb695e956c0c96a20c694f7696c013caac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Sat, 1 Feb 2025 21:42:19 +0900 Subject: [PATCH 040/115] =?UTF-8?q?[feat/#27]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- build.gradle | 10 +++ .../domain/concept/domain/QConceptTag.java | 47 ++++++++++ .../domain/member/domain/QMember.java | 53 ++++++++++++ .../domain/problem/domain/QAnswer.java | 37 ++++++++ .../domain/childProblem/QChildProblem.java | 67 +++++++++++++++ .../domain/practiceTest/QPracticeTestTag.java | 47 ++++++++++ .../problem/domain/problem/QProblem.java | 86 +++++++++++++++++++ .../problem/domain/problem/QProblemId.java | 37 ++++++++ .../mapper/ChildProblemMapperImpl.java | 2 +- .../service/mapper/ProblemMapperImpl.java | 2 +- .../entity/QDetailResultApplication.java | 51 +++++++++++ .../TestResult/entity/QEstimatedRating.java | 43 ++++++++++ .../TestResult/entity/QIncorrectProblem.java | 71 +++++++++++++++ .../v0/TestResult/entity/QTestResult.java | 51 +++++++++++ .../v0/practiceTest/domain/QPracticeTest.java | 61 +++++++++++++ .../practiceTest/domain/QProblemForTest.java | 82 ++++++++++++++++++ .../domain/QProblemImageForTest.java | 43 ++++++++++ .../v0/practiceTest/domain/QRatingTable.java | 54 ++++++++++++ .../global/common/QBaseEntity.java | 39 +++++++++ .../controller/ProblemSearchController.java | 34 ++++++++ .../domain/practiceTest/PracticeTestTag.java | 1 + .../response/ConceptTagSearchResponse.java | 16 ++++ .../response/ProblemSearchGetResponse.java | 24 ++++++ .../problem/repository/ProblemRepository.java | 3 +- .../repository/ProblemSearchRepository.java | 8 ++ .../ProblemSearchRepositoryImpl.java | 73 ++++++++++++++++ .../config/querydsl/QuerydslConfig.java | 20 +++++ .../ProblemSearchRepositoryImplTest.java | 73 ++++++++++++++++ .../service/ProblemSaveServiceTest.java | 4 +- src/test/resources/application-h2test.yml | 5 ++ src/test/resources/concept-tag.sql | 19 ++-- src/test/resources/insert-problem.sql | 10 ++- src/test/resources/practice-test-tag.sql | 4 +- 34 files changed, 1155 insertions(+), 25 deletions(-) create mode 100644 src/main/generated/com/moplus/moplus_server/domain/concept/domain/QConceptTag.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/member/domain/QMember.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/QAnswer.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/practiceTest/QPracticeTestTag.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/v0/DetailResultApplication/entity/QDetailResultApplication.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QEstimatedRating.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QTestResult.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QPracticeTest.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemForTest.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QRatingTable.java create mode 100644 src/main/generated/com/moplus/moplus_server/global/common/QBaseEntity.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ConceptTagSearchResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepository.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImpl.java create mode 100644 src/main/java/com/moplus/moplus_server/global/config/querydsl/QuerydslConfig.java create mode 100644 src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImplTest.java diff --git a/.gitignore b/.gitignore index 4125023..227c6ad 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ - .env ### STS ### @@ -37,3 +36,5 @@ out/ ### VS Code ### .vscode/ + +src/test/resources/insert-search-problem.sql diff --git a/build.gradle b/build.gradle index f4e44f9..4409a35 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ repositories { mavenCentral() } + dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' @@ -61,6 +62,15 @@ dependencies { annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0' + // JPA + implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0' + implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1' + + // QueryDSL + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" } tasks.named('test') { diff --git a/src/main/generated/com/moplus/moplus_server/domain/concept/domain/QConceptTag.java b/src/main/generated/com/moplus/moplus_server/domain/concept/domain/QConceptTag.java new file mode 100644 index 0000000..454956c --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/concept/domain/QConceptTag.java @@ -0,0 +1,47 @@ +package com.moplus.moplus_server.domain.concept.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QConceptTag is a Querydsl query type for ConceptTag + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QConceptTag extends EntityPathBase { + + private static final long serialVersionUID = 652954745L; + + public static final QConceptTag conceptTag = new QConceptTag("conceptTag"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath name = createString("name"); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QConceptTag(String variable) { + super(ConceptTag.class, forVariable(variable)); + } + + public QConceptTag(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QConceptTag(PathMetadata metadata) { + super(ConceptTag.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/member/domain/QMember.java b/src/main/generated/com/moplus/moplus_server/domain/member/domain/QMember.java new file mode 100644 index 0000000..ad5aeae --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/member/domain/QMember.java @@ -0,0 +1,53 @@ +package com.moplus.moplus_server.domain.member.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QMember is a Querydsl query type for Member + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QMember extends EntityPathBase { + + private static final long serialVersionUID = -705761779L; + + public static final QMember member = new QMember("member1"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final StringPath email = createString("email"); + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath name = createString("name"); + + public final StringPath password = createString("password"); + + public final EnumPath role = createEnum("role", MemberRole.class); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QMember(String variable) { + super(Member.class, forVariable(variable)); + } + + public QMember(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QMember(PathMetadata metadata) { + super(Member.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/QAnswer.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/QAnswer.java new file mode 100644 index 0000000..cdda3fe --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/QAnswer.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QAnswer is a Querydsl query type for Answer + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QAnswer extends BeanPath { + + private static final long serialVersionUID = 983834524L; + + public static final QAnswer answer = new QAnswer("answer"); + + public final StringPath value = createString("value"); + + public QAnswer(String variable) { + super(Answer.class, forVariable(variable)); + } + + public QAnswer(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QAnswer(PathMetadata metadata) { + super(Answer.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java new file mode 100644 index 0000000..8dad7ae --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java @@ -0,0 +1,67 @@ +package com.moplus.moplus_server.domain.problem.domain.childProblem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QChildProblem is a Querydsl query type for ChildProblem + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QChildProblem extends EntityPathBase { + + private static final long serialVersionUID = 1030139824L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QChildProblem childProblem = new QChildProblem("childProblem"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + public final com.moplus.moplus_server.domain.problem.domain.QAnswer answer; + + public final SetPath> conceptTagIds = this.>createSet("conceptTagIds", Long.class, NumberPath.class, PathInits.DIRECT2); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath imageUrl = createString("imageUrl"); + + public final EnumPath problemType = createEnum("problemType", com.moplus.moplus_server.domain.problem.domain.problem.ProblemType.class); + + public final NumberPath sequence = createNumber("sequence", Integer.class); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QChildProblem(String variable) { + this(ChildProblem.class, forVariable(variable), INITS); + } + + public QChildProblem(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QChildProblem(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QChildProblem(PathMetadata metadata, PathInits inits) { + this(ChildProblem.class, metadata, inits); + } + + public QChildProblem(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.answer = inits.isInitialized("answer") ? new com.moplus.moplus_server.domain.problem.domain.QAnswer(forProperty("answer")) : null; + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/practiceTest/QPracticeTestTag.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/practiceTest/QPracticeTestTag.java new file mode 100644 index 0000000..83588ac --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/practiceTest/QPracticeTestTag.java @@ -0,0 +1,47 @@ +package com.moplus.moplus_server.domain.problem.domain.practiceTest; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QPracticeTestTag is a Querydsl query type for PracticeTestTag + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QPracticeTestTag extends EntityPathBase { + + private static final long serialVersionUID = -2120162934L; + + public static final QPracticeTestTag practiceTestTag = new QPracticeTestTag("practiceTestTag"); + + public final StringPath area = createString("area"); + + public final NumberPath id = createNumber("id", Long.class); + + public final NumberPath month = createNumber("month", Integer.class); + + public final StringPath name = createString("name"); + + public final EnumPath subject = createEnum("subject", Subject.class); + + public final NumberPath year = createNumber("year", Integer.class); + + public QPracticeTestTag(String variable) { + super(PracticeTestTag.class, forVariable(variable)); + } + + public QPracticeTestTag(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QPracticeTestTag(PathMetadata metadata) { + super(PracticeTestTag.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java new file mode 100644 index 0000000..fb35ae5 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java @@ -0,0 +1,86 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QProblem is a Querydsl query type for Problem + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QProblem extends EntityPathBase { + + private static final long serialVersionUID = -1319796686L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QProblem problem = new QProblem("problem"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + public final com.moplus.moplus_server.domain.problem.domain.QAnswer answer; + + public final ListPath childProblems = this.createList("childProblems", com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem.class, com.moplus.moplus_server.domain.problem.domain.childProblem.QChildProblem.class, PathInits.DIRECT2); + + public final StringPath comment = createString("comment"); + + public final SetPath> conceptTagIds = this.>createSet("conceptTagIds", Long.class, NumberPath.class, PathInits.DIRECT2); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final QProblemId id; + + public final BooleanPath isPublished = createBoolean("isPublished"); + + public final BooleanPath isVariation = createBoolean("isVariation"); + + public final StringPath mainAnalysisImageUrl = createString("mainAnalysisImageUrl"); + + public final StringPath mainProblemImageUrl = createString("mainProblemImageUrl"); + + public final NumberPath number = createNumber("number", Integer.class); + + public final NumberPath practiceTestId = createNumber("practiceTestId", Long.class); + + public final StringPath prescriptionImageUrl = createString("prescriptionImageUrl"); + + public final EnumPath problemType = createEnum("problemType", ProblemType.class); + + public final StringPath readingTipImageUrl = createString("readingTipImageUrl"); + + public final StringPath seniorTipImageUrl = createString("seniorTipImageUrl"); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QProblem(String variable) { + this(Problem.class, forVariable(variable), INITS); + } + + public QProblem(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QProblem(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QProblem(PathMetadata metadata, PathInits inits) { + this(Problem.class, metadata, inits); + } + + public QProblem(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.answer = inits.isInitialized("answer") ? new com.moplus.moplus_server.domain.problem.domain.QAnswer(forProperty("answer")) : null; + this.id = inits.isInitialized("id") ? new QProblemId(forProperty("id")) : null; + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java new file mode 100644 index 0000000..ca7809a --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QProblemId is a Querydsl query type for ProblemId + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QProblemId extends BeanPath { + + private static final long serialVersionUID = -1309260563L; + + public static final QProblemId problemId = new QProblemId("problemId"); + + public final StringPath id = createString("id"); + + public QProblemId(String variable) { + super(ProblemId.class, forVariable(variable)); + } + + public QProblemId(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QProblemId(PathMetadata metadata) { + super(ProblemId.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java index e76d12e..53469fa 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java @@ -10,7 +10,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-01-30T21:11:35+0900", + date = "2025-01-31T02:55:57+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index dc059cb..ba945c8 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -12,7 +12,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-01-30T21:11:35+0900", + date = "2025-01-31T02:55:56+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/DetailResultApplication/entity/QDetailResultApplication.java b/src/main/generated/com/moplus/moplus_server/domain/v0/DetailResultApplication/entity/QDetailResultApplication.java new file mode 100644 index 0000000..9ff44d0 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/DetailResultApplication/entity/QDetailResultApplication.java @@ -0,0 +1,51 @@ +package com.moplus.moplus_server.domain.v0.DetailResultApplication.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QDetailResultApplication is a Querydsl query type for DetailResultApplication + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QDetailResultApplication extends EntityPathBase { + + private static final long serialVersionUID = 215702330L; + + public static final QDetailResultApplication detailResultApplication = new QDetailResultApplication("detailResultApplication"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath name = createString("name"); + + public final StringPath phoneNumber = createString("phoneNumber"); + + public final NumberPath testResultId = createNumber("testResultId", Long.class); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QDetailResultApplication(String variable) { + super(DetailResultApplication.class, forVariable(variable)); + } + + public QDetailResultApplication(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QDetailResultApplication(PathMetadata metadata) { + super(DetailResultApplication.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QEstimatedRating.java b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QEstimatedRating.java new file mode 100644 index 0000000..7dbaf22 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QEstimatedRating.java @@ -0,0 +1,43 @@ +package com.moplus.moplus_server.domain.v0.TestResult.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QEstimatedRating is a Querydsl query type for EstimatedRating + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QEstimatedRating extends EntityPathBase { + + private static final long serialVersionUID = -1923088138L; + + public static final QEstimatedRating estimatedRating1 = new QEstimatedRating("estimatedRating1"); + + public final NumberPath estimatedRating = createNumber("estimatedRating", Integer.class); + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath ratingProvider = createString("ratingProvider"); + + public final NumberPath testResultId = createNumber("testResultId", Long.class); + + public QEstimatedRating(String variable) { + super(EstimatedRating.class, forVariable(variable)); + } + + public QEstimatedRating(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QEstimatedRating(PathMetadata metadata) { + super(EstimatedRating.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java new file mode 100644 index 0000000..b73be8c --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java @@ -0,0 +1,71 @@ +package com.moplus.moplus_server.domain.v0.TestResult.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QIncorrectProblem is a Querydsl query type for IncorrectProblem + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QIncorrectProblem extends EntityPathBase { + + private static final long serialVersionUID = 1019670237L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QIncorrectProblem incorrectProblem = new QIncorrectProblem("incorrectProblem"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + public final NumberPath correctRate = createNumber("correctRate", Double.class); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath incorrectAnswer = createString("incorrectAnswer"); + + public final NumberPath point = createNumber("point", Integer.class); + + public final NumberPath practiceTestId = createNumber("practiceTestId", Long.class); + + public final NumberPath problemId = createNumber("problemId", Long.class); + + public final StringPath problemNumber = createString("problemNumber"); + + public final QTestResult testResult; + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QIncorrectProblem(String variable) { + this(IncorrectProblem.class, forVariable(variable), INITS); + } + + public QIncorrectProblem(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QIncorrectProblem(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QIncorrectProblem(PathMetadata metadata, PathInits inits) { + this(IncorrectProblem.class, metadata, inits); + } + + public QIncorrectProblem(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.testResult = inits.isInitialized("testResult") ? new QTestResult(forProperty("testResult")) : null; + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QTestResult.java b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QTestResult.java new file mode 100644 index 0000000..5f8fb8b --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QTestResult.java @@ -0,0 +1,51 @@ +package com.moplus.moplus_server.domain.v0.TestResult.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QTestResult is a Querydsl query type for TestResult + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QTestResult extends EntityPathBase { + + private static final long serialVersionUID = -1200752334L; + + public static final QTestResult testResult = new QTestResult("testResult"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final NumberPath id = createNumber("id", Long.class); + + public final NumberPath practiceTestId = createNumber("practiceTestId", Long.class); + + public final NumberPath score = createNumber("score", Integer.class); + + public final ComparablePath solvingTime = createComparable("solvingTime", java.time.Duration.class); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QTestResult(String variable) { + super(TestResult.class, forVariable(variable)); + } + + public QTestResult(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QTestResult(PathMetadata metadata) { + super(TestResult.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QPracticeTest.java b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QPracticeTest.java new file mode 100644 index 0000000..38ac923 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QPracticeTest.java @@ -0,0 +1,61 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QPracticeTest is a Querydsl query type for PracticeTest + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QPracticeTest extends EntityPathBase { + + private static final long serialVersionUID = -700271987L; + + public static final QPracticeTest practiceTest = new QPracticeTest("practiceTest"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + public final ComparablePath averageSolvingTime = createComparable("averageSolvingTime", java.time.Duration.class); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath name = createString("name"); + + public final StringPath provider = createString("provider"); + + public final NumberPath publicationYear = createNumber("publicationYear", Integer.class); + + public final StringPath round = createString("round"); + + public final NumberPath solvesCount = createNumber("solvesCount", Integer.class); + + public final EnumPath subject = createEnum("subject", com.moplus.moplus_server.domain.problem.domain.practiceTest.Subject.class); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public final NumberPath viewCount = createNumber("viewCount", Long.class); + + public QPracticeTest(String variable) { + super(PracticeTest.class, forVariable(variable)); + } + + public QPracticeTest(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QPracticeTest(PathMetadata metadata) { + super(PracticeTest.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemForTest.java b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemForTest.java new file mode 100644 index 0000000..031ef66 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemForTest.java @@ -0,0 +1,82 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QProblemForTest is a Querydsl query type for ProblemForTest + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QProblemForTest extends EntityPathBase { + + private static final long serialVersionUID = 159101820L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QProblemForTest problemForTest = new QProblemForTest("problemForTest"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + public final StringPath answer = createString("answer"); + + public final EnumPath answerFormat = createEnum("answerFormat", AnswerFormat.class); + + public final StringPath conceptType = createString("conceptType"); + + public final NumberPath correctRate = createNumber("correctRate", Double.class); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final NumberPath id = createNumber("id", Long.class); + + public final QProblemImageForTest image; + + public final NumberPath incorrectNum = createNumber("incorrectNum", Long.class); + + public final NumberPath point = createNumber("point", Integer.class); + + public final QPracticeTest practiceTest; + + public final StringPath problemNumber = createString("problemNumber"); + + public final EnumPath problemRating = createEnum("problemRating", ProblemRating.class); + + public final StringPath subunit = createString("subunit"); + + public final StringPath unit = createString("unit"); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QProblemForTest(String variable) { + this(ProblemForTest.class, forVariable(variable), INITS); + } + + public QProblemForTest(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QProblemForTest(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QProblemForTest(PathMetadata metadata, PathInits inits) { + this(ProblemForTest.class, metadata, inits); + } + + public QProblemForTest(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.image = inits.isInitialized("image") ? new QProblemImageForTest(forProperty("image")) : null; + this.practiceTest = inits.isInitialized("practiceTest") ? new QPracticeTest(forProperty("practiceTest")) : null; + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java new file mode 100644 index 0000000..b7e7668 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java @@ -0,0 +1,43 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QProblemImageForTest is a Querydsl query type for ProblemImageForTest + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QProblemImageForTest extends EntityPathBase { + + private static final long serialVersionUID = 1499588927L; + + public static final QProblemImageForTest problemImageForTest = new QProblemImageForTest("problemImageForTest"); + + public final StringPath fileName = createString("fileName"); + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath imageUrl = createString("imageUrl"); + + public final NumberPath problemId = createNumber("problemId", Long.class); + + public QProblemImageForTest(String variable) { + super(ProblemImageForTest.class, forVariable(variable)); + } + + public QProblemImageForTest(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QProblemImageForTest(PathMetadata metadata) { + super(ProblemImageForTest.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QRatingTable.java b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QRatingTable.java new file mode 100644 index 0000000..842309d --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QRatingTable.java @@ -0,0 +1,54 @@ +package com.moplus.moplus_server.domain.v0.practiceTest.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QRatingTable is a Querydsl query type for RatingTable + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QRatingTable extends EntityPathBase { + + private static final long serialVersionUID = -1985854959L; + + public static final QRatingTable ratingTable = new QRatingTable("ratingTable"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final NumberPath id = createNumber("id", Long.class); + + public final NumberPath practiceTestId = createNumber("practiceTestId", Long.class); + + public final StringPath ratingProvider = createString("ratingProvider"); + + public final ListPath> ratingRows = this.>createList("ratingRows", RatingRow.class, SimplePath.class, PathInits.DIRECT2); + + public final EnumPath subject = createEnum("subject", com.moplus.moplus_server.domain.problem.domain.practiceTest.Subject.class); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QRatingTable(String variable) { + super(RatingTable.class, forVariable(variable)); + } + + public QRatingTable(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QRatingTable(PathMetadata metadata) { + super(RatingTable.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/global/common/QBaseEntity.java b/src/main/generated/com/moplus/moplus_server/global/common/QBaseEntity.java new file mode 100644 index 0000000..8c5284b --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/global/common/QBaseEntity.java @@ -0,0 +1,39 @@ +package com.moplus.moplus_server.global.common; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QBaseEntity is a Querydsl query type for BaseEntity + */ +@Generated("com.querydsl.codegen.DefaultSupertypeSerializer") +public class QBaseEntity extends EntityPathBase { + + private static final long serialVersionUID = -1014955751L; + + public static final QBaseEntity baseEntity = new QBaseEntity("baseEntity"); + + public final DateTimePath createdDate = createDateTime("createdDate", java.time.LocalDateTime.class); + + public final DateTimePath updatedDate = createDateTime("updatedDate", java.time.LocalDateTime.class); + + public QBaseEntity(String variable) { + super(BaseEntity.class, forVariable(variable)); + } + + public QBaseEntity(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QBaseEntity(PathMetadata metadata) { + super(BaseEntity.class, metadata); + } + +} + diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java new file mode 100644 index 0000000..cf8c088 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java @@ -0,0 +1,34 @@ +package com.moplus.moplus_server.domain.problem.controller; + +import com.moplus.moplus_server.domain.problem.dto.response.ProblemSearchGetResponse; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import io.swagger.v3.oas.annotations.Operation; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/problems") +@RequiredArgsConstructor +public class ProblemSearchController { + + private final ProblemRepository problemRepository; + + @GetMapping("/search") + @Operation( + summary = "문제 검색", + description = "문항 ID, 문제명, 개념 태그리스트로 문제를 검색합니다. 개념 태그리스트는 OR 조건으로 검색하며 값이 없으면 쿼리파라미터에서 빼주세요" + ) + public ResponseEntity> search( + @RequestParam(value = "problemId", required = false) String problemId, + @RequestParam(value = "comment", required = false) String comment, + @RequestParam(value = "conceptTagIds", required = false) List conceptTagIds + ) { + List problems = problemRepository.search(problemId, comment, conceptTagIds); + return ResponseEntity.ok(problems); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java index 1782bd8..b21f78b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java @@ -19,6 +19,7 @@ public class PracticeTestTag { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "practice_test_tag_id") private Long id; private String name; diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ConceptTagSearchResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ConceptTagSearchResponse.java new file mode 100644 index 0000000..75e5c73 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ConceptTagSearchResponse.java @@ -0,0 +1,16 @@ +package com.moplus.moplus_server.domain.problem.dto.response; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ConceptTagSearchResponse { + private Long id; + private String name; // 예시로 태그 이름을 추가 (필요에 따라 변경 가능) + + public ConceptTagSearchResponse(Long id, String name) { + this.id = id; + this.name = name; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java new file mode 100644 index 0000000..7a1710b --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java @@ -0,0 +1,24 @@ +package com.moplus.moplus_server.domain.problem.dto.response; + +import java.util.Set; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ProblemSearchGetResponse { + private String problemId; + private String comment; + private String mainProblemImageUrl; + private Set conceptTagResponses; + + public ProblemSearchGetResponse(String problemId, String comment, String mainProblemImageUrl, + Set conceptTagResponses) { + this.problemId = problemId; + this.comment = comment; + this.mainProblemImageUrl = mainProblemImageUrl; + this.conceptTagResponses = conceptTagResponses; + } +} + + diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java index 494f0c9..1be5b7b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java @@ -7,7 +7,7 @@ import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; -public interface ProblemRepository extends JpaRepository { +public interface ProblemRepository extends JpaRepository, ProblemSearchRepository { boolean existsByPracticeTestIdAndNumber(Long practiceTestId, int number); @@ -23,7 +23,6 @@ default void existsByIdElseThrow(ProblemId problemId) { } } - default Problem findByIdElseThrow(ProblemId problemId) { return findById(problemId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepository.java new file mode 100644 index 0000000..71b3939 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepository.java @@ -0,0 +1,8 @@ +package com.moplus.moplus_server.domain.problem.repository; + +import com.moplus.moplus_server.domain.problem.dto.response.ProblemSearchGetResponse; +import java.util.List; + +public interface ProblemSearchRepository { + List search(String problemId, String comment, List conceptTagIds); +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImpl.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImpl.java new file mode 100644 index 0000000..86ecae2 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImpl.java @@ -0,0 +1,73 @@ +package com.moplus.moplus_server.domain.problem.repository; + +import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag; +import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; + +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.dto.response.ConceptTagSearchResponse; +import com.moplus.moplus_server.domain.problem.dto.response.ProblemSearchGetResponse; +import com.querydsl.core.group.GroupBy; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; +import org.springframework.stereotype.Repository; + +@Repository +public class ProblemSearchRepositoryImpl extends QuerydslRepositorySupport implements ProblemSearchRepository { + + private final JPAQueryFactory queryFactory; + + @Autowired + public ProblemSearchRepositoryImpl(JPAQueryFactory queryFactory) { + super(Problem.class); + this.queryFactory = queryFactory; + } + + @Override + public List search(String problemId, String comment, List conceptTagIds) { + return queryFactory + .from(problem) + .where( + containsProblemId(problemId), + containsName(comment), + inConceptTagIds(conceptTagIds) + ) + .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) + .distinct() + .transform(GroupBy.groupBy(problem.id.id).list( + Projections.constructor(ProblemSearchGetResponse.class, + problem.id.id, + problem.comment, + problem.mainProblemImageUrl, + GroupBy.set( + Projections.constructor(ConceptTagSearchResponse.class, + conceptTag.id, + conceptTag.name + ) + ) + ) + )); + } + + //problemId 일부 포함 검색 + private BooleanExpression containsProblemId(String problemId) { + return (problemId == null || problemId.isEmpty()) ? null : problem.id.id.containsIgnoreCase(problemId); + } + + //name 조건 (포함 검색) + private BooleanExpression containsName(String comment) { + if (comment == null || comment.trim().isEmpty()) { + return null; + } + return problem.comment.containsIgnoreCase(comment.trim()); + } + + //conceptTagIds 조건 (하나라도 포함되면 조회) + private BooleanExpression inConceptTagIds(List conceptTagIds) { + return (conceptTagIds == null || conceptTagIds.isEmpty()) ? null + : problem.conceptTagIds.any().in(conceptTagIds); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/config/querydsl/QuerydslConfig.java b/src/main/java/com/moplus/moplus_server/global/config/querydsl/QuerydslConfig.java new file mode 100644 index 0000000..2889116 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/config/querydsl/QuerydslConfig.java @@ -0,0 +1,20 @@ +package com.moplus.moplus_server.global.config.querydsl; + +import com.querydsl.jpa.JPQLTemplates; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager); + } +} diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImplTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImplTest.java new file mode 100644 index 0000000..8a80fd8 --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImplTest.java @@ -0,0 +1,73 @@ +package com.moplus.moplus_server.domain.problem.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.moplus.moplus_server.domain.problem.dto.response.ProblemSearchGetResponse; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@SpringBootTest +@ActiveProfiles("h2test") +@Sql({"/practice-test-tag.sql", "/concept-tag.sql", "/insert-problem.sql"}) +public class ProblemSearchRepositoryImplTest { + + @Autowired + private ProblemRepository problemSearchRepository; + + @Test + void problemId_일부_포함_검색() { + // when + List result = problemSearchRepository.search("240520012", null, null); + + // then + assertThat(result).hasSize(2); + assertThat(result).extracting(ProblemSearchGetResponse::getProblemId) + .containsExactlyInAnyOrder("240520012001", "240520012002"); + } + + @Test + void name_포함_검색() { + // when + List result = problemSearchRepository.search(null, "설명 1 ", null); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).getProblemId()).isEqualTo("240520012001"); + } + + @Test + void conceptTagIds_하나라도_포함되면_조회() { + // when + List result = problemSearchRepository.search(null, null, List.of(3L)); + + // then + assertThat(result).hasSize(2); + assertThat(result).extracting(ProblemSearchGetResponse::getProblemId) + .containsExactlyInAnyOrder("240520012001", "240520012002"); + } + + @Test + void problemId_이름_conceptTagIds_모두_적용된_검색() { + // when + List result = problemSearchRepository.search("2405200120", "설명 1", List.of(1L)); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).getProblemId()).isEqualTo("240520012001"); + } + + @Test + void 아무_조건도_없으면_모든_데이터_조회() { + // when + List result = problemSearchRepository.search(null, null, null); + + // then + assertThat(result).hasSize(2); + } +} \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java index 65db7b9..63abbe6 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java @@ -16,7 +16,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.transaction.annotation.Transactional; @@ -80,10 +79,10 @@ void setUp() { "prescription2.png", List.of(childProblem3, childProblem2, childProblem4, childProblem1) // 🔹 순서 유지 (0,1,2,3) ); + } @Test - @Rollback(value = false) void 정상동작() { // when @@ -113,7 +112,6 @@ void setUp() { } @Test - @Rollback(true) void 자식문제_올바른_순서_저장() { // when ProblemId createdProblemId = problemSaveService.createProblem(problemPostRequestOutOfOrder); diff --git a/src/test/resources/application-h2test.yml b/src/test/resources/application-h2test.yml index f1bf6e6..6095227 100644 --- a/src/test/resources/application-h2test.yml +++ b/src/test/resources/application-h2test.yml @@ -3,6 +3,11 @@ spring: hibernate: ddl-auto: create-drop show-sql: true + properties: + hibernate: + format_sql: true + use_sql_comments: true + highlight_sql: true datasource: url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false;MODE=MYSQL diff --git a/src/test/resources/concept-tag.sql b/src/test/resources/concept-tag.sql index 6b94678..1d06a9a 100644 --- a/src/test/resources/concept-tag.sql +++ b/src/test/resources/concept-tag.sql @@ -1,12 +1,7 @@ -INSERT INTO concept_tag (name) -VALUES ('미분 개념'); -INSERT INTO concept_tag (name) -VALUES ('적분 개념'); -INSERT INTO concept_tag (name) -VALUES ('삼각함수 개념'); -INSERT INTO concept_tag (name) -VALUES ('행렬 개념'); -INSERT INTO concept_tag (name) -VALUES ('확률과 통계 개념'); -INSERT INTO concept_tag (name) -VALUES ('기하 개념'); \ No newline at end of file +INSERT INTO concept_tag (concept_tag_id, name) +VALUES (1, '미분 개념'), + (2, '적분 개념'), + (3, '삼각함수 개념'), + (4, '행렬 개념'), + (5, '확률과 통계 개념'), + (6, '기하 개념'); \ No newline at end of file diff --git a/src/test/resources/insert-problem.sql b/src/test/resources/insert-problem.sql index 9f81e33..530736b 100644 --- a/src/test/resources/insert-problem.sql +++ b/src/test/resources/insert-problem.sql @@ -6,10 +6,12 @@ FROM child_problem; INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, is_published, is_variation) -VALUES ('240520012001', 1, 1, '1', '기존 문제 설명', +VALUES ('240520012001', 1, 1, '1', '기존 문제 설명 1', + 'mainProblem.png', 'mainAnalysis.png', 'readingTip.png', 'seniorTip.png', 'prescription.png', + false, false), + ('240520012002', 1, 1, '1', '기존 문제 설명 2', 'mainProblem.png', 'mainAnalysis.png', 'readingTip.png', 'seniorTip.png', 'prescription.png', false, false); - -- ✅ 기존 자식 문제(ChildProblem) 삽입 INSERT INTO child_problem (child_problem_id, problem_id, image_url, problem_type, answer, sequence) VALUES (1, '240520012001', 'child1.png', 'MULTIPLE_CHOICE', '1', 0), @@ -19,7 +21,9 @@ VALUES (1, '240520012001', 'child1.png', 'MULTIPLE_CHOICE', '1', 0), INSERT INTO problem_concept (problem_id, concept_tag_id) VALUES ('240520012001', 1), ('240520012001', 2), - ('240520012001', 3); + ('240520012001', 3), + ('240520012002', 1), + ('240520012002', 3); -- ✅ 자식 문제-컨셉 태그 연결 INSERT INTO child_problem_concept (child_problem_id, concept_tag_id) diff --git a/src/test/resources/practice-test-tag.sql b/src/test/resources/practice-test-tag.sql index a3ae1b6..58625ce 100644 --- a/src/test/resources/practice-test-tag.sql +++ b/src/test/resources/practice-test-tag.sql @@ -1,2 +1,2 @@ -INSERT INTO practice_test_tag (name, test_year, test_month, subject, area) -VALUES ('2025년 5월 고2 모의고사', 2024, 5, '고2', '수학'); \ No newline at end of file +INSERT INTO practice_test_tag (practice_test_tag_id, name, test_year, test_month, subject, area) +VALUES (1, '2025년 5월 고2 모의고사', 2024, 5, '고2', '수학'); \ No newline at end of file From 7f2b389502d921cdb6874087423fae8ffda3ddaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Sun, 2 Feb 2025 01:33:33 +0900 Subject: [PATCH 041/115] =?UTF-8?q?[feat/#27]=20querydsl=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=97=90=EC=84=9C=20JpaQueryFactory=20?= =?UTF-8?q?=EB=B9=88=20=EC=A3=BC=EC=9E=85=20=EB=B0=A9=EC=8B=9D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problem/repository/ProblemSearchRepository.java | 8 -------- ...sitoryImpl.java => ProblemSearchRepositoryCustom.java} | 7 ++++--- ...plTest.java => ProblemSearchRepositoryCustomTest.java} | 0 3 files changed, 4 insertions(+), 11 deletions(-) delete mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepository.java rename src/main/java/com/moplus/moplus_server/domain/problem/repository/{ProblemSearchRepositoryImpl.java => ProblemSearchRepositoryCustom.java} (93%) rename src/test/java/com/moplus/moplus_server/domain/problem/repository/{ProblemSearchRepositoryImplTest.java => ProblemSearchRepositoryCustomTest.java} (100%) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepository.java deleted file mode 100644 index 71b3939..0000000 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.moplus.moplus_server.domain.problem.repository; - -import com.moplus.moplus_server.domain.problem.dto.response.ProblemSearchGetResponse; -import java.util.List; - -public interface ProblemSearchRepository { - List search(String problemId, String comment, List conceptTagIds); -} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImpl.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java similarity index 93% rename from src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImpl.java rename to src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java index 86ecae2..43a7e45 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImpl.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java @@ -11,12 +11,13 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; import org.springframework.stereotype.Repository; @Repository -public class ProblemSearchRepositoryImpl extends QuerydslRepositorySupport implements ProblemSearchRepository { +@RequiredArgsConstructor +public class ProblemSearchRepositoryImpl { private final JPAQueryFactory queryFactory; @@ -35,7 +36,7 @@ public List search(String problemId, String comment, L containsName(comment), inConceptTagIds(conceptTagIds) ) - .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) + .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)).fetchJoin() .distinct() .transform(GroupBy.groupBy(problem.id.id).list( Projections.constructor(ProblemSearchGetResponse.class, diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImplTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java similarity index 100% rename from src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryImplTest.java rename to src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java From f1ad376cbde98b1d1d59640e29aa7a7ca786414c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Sun, 2 Feb 2025 01:38:21 +0900 Subject: [PATCH 042/115] =?UTF-8?q?[fix/#27]=20=ED=95=84=EC=9A=94=ED=95=9C?= =?UTF-8?q?=20column=EB=A7=8C=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problem/controller/ProblemSearchController.java | 6 +++--- .../domain/problem/repository/ProblemRepository.java | 2 +- .../repository/ProblemSearchRepositoryCustom.java | 12 ++---------- .../global/config/querydsl/QuerydslConfig.java | 4 ++-- .../ProblemSearchRepositoryCustomTest.java | 4 ++-- 5 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java index cf8c088..7c8c50d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.problem.controller; import com.moplus.moplus_server.domain.problem.dto.response.ProblemSearchGetResponse; -import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.domain.problem.repository.ProblemSearchRepositoryCustom; import io.swagger.v3.oas.annotations.Operation; import java.util.List; import lombok.RequiredArgsConstructor; @@ -16,7 +16,7 @@ @RequiredArgsConstructor public class ProblemSearchController { - private final ProblemRepository problemRepository; + private final ProblemSearchRepositoryCustom problemSearchRepository; @GetMapping("/search") @Operation( @@ -28,7 +28,7 @@ public ResponseEntity> search( @RequestParam(value = "comment", required = false) String comment, @RequestParam(value = "conceptTagIds", required = false) List conceptTagIds ) { - List problems = problemRepository.search(problemId, comment, conceptTagIds); + List problems = problemSearchRepository.search(problemId, comment, conceptTagIds); return ResponseEntity.ok(problems); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java index 1be5b7b..a45c8f4 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java @@ -7,7 +7,7 @@ import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; -public interface ProblemRepository extends JpaRepository, ProblemSearchRepository { +public interface ProblemRepository extends JpaRepository { boolean existsByPracticeTestIdAndNumber(Long practiceTestId, int number); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java index 43a7e45..97af1a4 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java @@ -3,7 +3,6 @@ import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag; import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; -import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.dto.response.ConceptTagSearchResponse; import com.moplus.moplus_server.domain.problem.dto.response.ProblemSearchGetResponse; import com.querydsl.core.group.GroupBy; @@ -12,24 +11,17 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @Repository @RequiredArgsConstructor -public class ProblemSearchRepositoryImpl { +public class ProblemSearchRepositoryCustom { private final JPAQueryFactory queryFactory; - @Autowired - public ProblemSearchRepositoryImpl(JPAQueryFactory queryFactory) { - super(Problem.class); - this.queryFactory = queryFactory; - } - - @Override public List search(String problemId, String comment, List conceptTagIds) { return queryFactory + .select(problem.id.id, problem.comment, problem.mainProblemImageUrl) .from(problem) .where( containsProblemId(problemId), diff --git a/src/main/java/com/moplus/moplus_server/global/config/querydsl/QuerydslConfig.java b/src/main/java/com/moplus/moplus_server/global/config/querydsl/QuerydslConfig.java index 2889116..9f9ab4c 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/querydsl/QuerydslConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/querydsl/QuerydslConfig.java @@ -3,14 +3,14 @@ import com.querydsl.jpa.JPQLTemplates; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class QuerydslConfig { - @PersistenceContext + @Autowired private EntityManager entityManager; @Bean diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java index 8a80fd8..712115d 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java @@ -15,10 +15,10 @@ @SpringBootTest @ActiveProfiles("h2test") @Sql({"/practice-test-tag.sql", "/concept-tag.sql", "/insert-problem.sql"}) -public class ProblemSearchRepositoryImplTest { +public class ProblemSearchRepositoryCustomTest { @Autowired - private ProblemRepository problemSearchRepository; + private ProblemSearchRepositoryCustom problemSearchRepository; @Test void problemId_일부_포함_검색() { From 154e9735f47f0383b444bf666d793b48817f80ff Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 2 Feb 2025 17:49:56 +0900 Subject: [PATCH 043/115] =?UTF-8?q?[feat/#31]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=BB=A8=ED=8E=8C=20=EC=8B=9C=20=EB=AC=B8?= =?UTF-8?q?=ED=95=AD=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problem/domain/problem/Problem.java | 7 +++++++ .../problemset/service/ProblemSetUpdateService.java | 13 +++++++++++++ .../global/error/exception/ErrorCode.java | 1 + 3 files changed, 21 insertions(+) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index b059bf3..4f4c2e2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -120,4 +120,11 @@ public void updateChildProblem(List inputChildProblems) { public void deleteChildProblem(List deleteChildProblems) { childProblems.removeIf(childProblem -> deleteChildProblems.contains(childProblem.getId())); } + + public boolean isValid() { + return answer != null && !answer.getValue().isEmpty() + && conceptTagIds != null && !conceptTagIds.isEmpty() + && mainProblemImageUrl != null && !mainProblemImageUrl.isEmpty() + && mainAnalysisImageUrl != null && !mainAnalysisImageUrl.isEmpty(); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java index 8b46030..b050265 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java @@ -1,11 +1,14 @@ package com.moplus.moplus_server.domain.problemset.service; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -48,8 +51,18 @@ public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) public boolean toggleConfirmProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + // 문항 유효성 검사 + for (ProblemId problemId : problemSet.getProblemIds()) { + Problem problem = problemRepository.findByIdElseThrow(problemId); + + if (!problem.isValid()) { + throw new InvalidValueException(ErrorCode.INVALID_CONFIRM_PROBLEM); + } + } + // 현재 상태 반전 (true → false, false → true) problemSet.toggleConfirm(!problemSet.isConfirmed()); return problemSet.isConfirmed(); } + } diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 1e539bf..93516b5 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -32,6 +32,7 @@ public enum ErrorCode { PROBLEM_ALREADY_EXIST(HttpStatus.CONFLICT, "해당 문제는 이미 존재합니다"), INVALID_MULTIPLE_CHOICE_ANSWER(HttpStatus.BAD_REQUEST, "객관식 문제의 정답은 1~5 사이의 숫자여야 합니다"), INVALID_SHORT_NUMBER_ANSWER(HttpStatus.BAD_REQUEST, "주관식 문제의 정답은 0~999 사이의 숫자여야 합니다"), + INVALID_CONFIRM_PROBLEM(HttpStatus.BAD_REQUEST, "문항의 모든 요소를 등록해야 컨펌을 완료할 수 있습니다."), //새끼 문항 CHILD_PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 새끼 문제를 찾을 수 없습니다"), From 48d1cb66dc3409fa32b7cc833a11aca99873c3b9 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 2 Feb 2025 17:51:40 +0900 Subject: [PATCH 044/115] =?UTF-8?q?[fix/#31]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problemset/dto/request/ProblemSetPostRequest.java | 4 ++-- .../domain/problemset/service/ProblemSetSaveService.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java index 01ff421..ef31ed2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java @@ -7,9 +7,9 @@ public record ProblemSetPostRequest( String problemSetName, List problems ) { - public ProblemSet toEntity(String problemSetName) { + public ProblemSet toEntity() { return ProblemSet.builder() - .name(problemSetName) + .name(this.problemSetName) .build(); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java index 10063ac..084257a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -31,7 +31,7 @@ public Long createProblemSet(ProblemSetPostRequest request) { problemIdList.forEach(problemRepository::findByIdElseThrow); // ProblemSet 생성 - ProblemSet problemSet = request.toEntity(request.problemSetName()); + ProblemSet problemSet = request.toEntity(); return problemSetRepository.save(problemSet).getId(); } From cfb450813ed960cc25007d1987a77bb3aea159f8 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 2 Feb 2025 18:38:45 +0900 Subject: [PATCH 045/115] =?UTF-8?q?[feat/#31]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=BB=A8=ED=8E=8C=20=EC=8B=9C=20=EC=BB=A8?= =?UTF-8?q?=ED=8E=8C=EC=83=81=ED=83=9C=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProblemSetController.java | 6 +++--- .../domain/problemset/domain/ProblemSet.java | 16 ++++++++++++---- .../domain/ProblemSetConfirmStatus.java | 6 ++++++ .../service/ProblemSetUpdateService.java | 9 ++++----- 4 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSetConfirmStatus.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java index 2af7db8..d101b45 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -1,5 +1,6 @@ package com.moplus.moplus_server.domain.problemset.controller; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; @@ -64,8 +65,7 @@ public ResponseEntity deleteProblemSet( @PutMapping("/{problemSetId}/confirm") @Operation(summary = "문항세트 컨펌 토글", description = "문항세트의 컨펌 상태를 토글합니다.") - public ResponseEntity toggleConfirmProblemSet(@PathVariable Long problemSetId) { - boolean updatedState = problemSetUpdateService.toggleConfirmProblemSet(problemSetId); - return ResponseEntity.ok(updatedState); + public ResponseEntity toggleConfirmProblemSet(@PathVariable Long problemSetId) { + return ResponseEntity.ok(problemSetUpdateService.toggleConfirmProblemSet(problemSetId)); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index d45cc27..4689315 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -6,6 +6,8 @@ import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -30,7 +32,9 @@ public class ProblemSet extends BaseEntity { private String name; private boolean isDeleted; - private boolean isConfirmed; + + @Enumerated(EnumType.STRING) + private ProblemSetConfirmStatus confirmStatus; @ElementCollection @CollectionTable(name = "problem_set_problems", joinColumns = @JoinColumn(name = "problem_set_id")) @@ -42,7 +46,7 @@ public class ProblemSet extends BaseEntity { public ProblemSet(String name) { this.name = name; this.isDeleted = false; - this.isConfirmed = false; + this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED; } public void updateProblemOrder(List newProblems) { @@ -54,8 +58,12 @@ public void deleteProblemSet() { this.isDeleted = true; } - public void toggleConfirm(boolean isConfirmed) { - this.isConfirmed = isConfirmed; + public void toggleConfirm() { + if (this.confirmStatus == ProblemSetConfirmStatus.CONFIRMED) { + this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED; + } else { + this.confirmStatus = ProblemSetConfirmStatus.CONFIRMED; + } } public void updateProblemSet(String name, List newProblems) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSetConfirmStatus.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSetConfirmStatus.java new file mode 100644 index 0000000..7dec452 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSetConfirmStatus.java @@ -0,0 +1,6 @@ +package com.moplus.moplus_server.domain.problemset.domain; + +public enum ProblemSetConfirmStatus { + CONFIRMED, + NOT_CONFIRMED +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java index b050265..a7c95f5 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java @@ -4,6 +4,7 @@ import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; @@ -48,7 +49,7 @@ public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) } @Transactional - public boolean toggleConfirmProblemSet(Long problemSetId) { + public ProblemSetConfirmStatus toggleConfirmProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); // 문항 유효성 검사 @@ -60,9 +61,7 @@ public boolean toggleConfirmProblemSet(Long problemSetId) { } } - // 현재 상태 반전 (true → false, false → true) - problemSet.toggleConfirm(!problemSet.isConfirmed()); - return problemSet.isConfirmed(); + problemSet.toggleConfirm(); + return problemSet.getConfirmStatus(); } - } From 16fd5d571ef6dba33aec3a60ddde77a7a1ce530a Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 2 Feb 2025 19:08:15 +0900 Subject: [PATCH 046/115] =?UTF-8?q?[fix/#31]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problem/domain/problem/Problem.java | 8 ++++++-- .../domain/problemset/domain/ProblemSet.java | 6 +++--- .../problemset/dto/request/ProblemSetPostRequest.java | 4 +++- .../domain/problemset/service/ProblemSetSaveService.java | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index 4f4c2e2..e8c7814 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -123,8 +123,12 @@ public void deleteChildProblem(List deleteChildProblems) { public boolean isValid() { return answer != null && !answer.getValue().isEmpty() - && conceptTagIds != null && !conceptTagIds.isEmpty() + && practiceTestId != null + && comment != null && !comment.isEmpty() + && readingTipImageUrl != null && !readingTipImageUrl.isEmpty() + && seniorTipImageUrl != null && !seniorTipImageUrl.isEmpty() + && prescriptionImageUrl != null && !prescriptionImageUrl.isEmpty() && mainProblemImageUrl != null && !mainProblemImageUrl.isEmpty() && mainAnalysisImageUrl != null && !mainAnalysisImageUrl.isEmpty(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index 4689315..f5f1bf2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -43,15 +43,15 @@ public class ProblemSet extends BaseEntity { private List problemIds = new ArrayList<>(); @Builder - public ProblemSet(String name) { + public ProblemSet(String name, List problemIds) { this.name = name; this.isDeleted = false; this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED; + this.problemIds = problemIds; } public void updateProblemOrder(List newProblems) { - this.problemIds.clear(); - this.problemIds.addAll(newProblems); + this.problemIds = new ArrayList<>(newProblems); } public void deleteProblemSet() { diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java index ef31ed2..0401915 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java @@ -1,5 +1,6 @@ package com.moplus.moplus_server.domain.problemset.dto.request; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import java.util.List; @@ -7,9 +8,10 @@ public record ProblemSetPostRequest( String problemSetName, List problems ) { - public ProblemSet toEntity() { + public ProblemSet toEntity(List problemIdList) { return ProblemSet.builder() .name(this.problemSetName) + .problemIds(problemIdList) .build(); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java index 084257a..3805f6e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -31,7 +31,7 @@ public Long createProblemSet(ProblemSetPostRequest request) { problemIdList.forEach(problemRepository::findByIdElseThrow); // ProblemSet 생성 - ProblemSet problemSet = request.toEntity(); + ProblemSet problemSet = request.toEntity(problemIdList); return problemSetRepository.save(problemSet).getId(); } From a0414727e62ed879655e790edf845c91a543cba5 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 2 Feb 2025 19:08:35 +0900 Subject: [PATCH 047/115] =?UTF-8?q?[test/#31]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problemset/ProblemSetServiceTest.java | 147 ++++++++++++++++++ src/test/resources/insert-problemset.sql | 40 +++++ 2 files changed, 187 insertions(+) create mode 100644 src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java create mode 100644 src/test/resources/insert-problemset.sql diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java new file mode 100644 index 0000000..cf36fe8 --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -0,0 +1,147 @@ +package com.moplus.moplus_server.domain.problemset; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; +import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService; +import com.moplus.moplus_server.domain.problemset.service.ProblemSetUpdateService; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@ActiveProfiles("h2test") +@Sql({"/insert-problemset.sql"}) +@SpringBootTest +public class ProblemSetServiceTest { + + @Autowired + private ProblemSetSaveService problemSetSaveService; + + @Autowired + private ProblemSetUpdateService problemSetUpdateService; + + @Autowired + private ProblemSetRepository problemSetRepository; + + private ProblemSetPostRequest problemSetPostRequest; + + @BeforeEach + void setUp() { + // 초기 문항 세트 생성 요청 데이터 준비 + problemSetPostRequest = new ProblemSetPostRequest( + "초기 문항세트", + List.of("24052001001", "24052001002", "24052001003") + ); + } + + @Test + @Rollback(false) + void 문항세트_생성_테스트() { + // when + Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); + + // then + ProblemSet savedProblemSet = problemSetRepository.findById(problemSetId) + .orElseThrow(() -> new IllegalArgumentException("문항세트를 찾을 수 없습니다.")); + + assertThat(savedProblemSet).isNotNull(); + assertThat(savedProblemSet.getName()).isEqualTo("초기 문항세트"); + assertThat(savedProblemSet.getProblemIds()).hasSize(3); + assertThat(savedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001001"); + assertThat(savedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001002"); + assertThat(savedProblemSet.getProblemIds().get(2).getId()).isEqualTo("24052001003"); + } + + @Test + @Rollback(true) + void 문항세트_문항순서_변경_테스트() { + // given + Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); + + // when + ProblemReorderRequest reorderRequest = new ProblemReorderRequest( + List.of("24052001003", "24052001001", "24052001002") + ); + problemSetUpdateService.reorderProblems(problemSetId, reorderRequest); + + // then + ProblemSet updatedProblemSet = problemSetRepository.findById(problemSetId) + .orElseThrow(() -> new IllegalArgumentException("문항세트를 찾을 수 없습니다.")); + + assertThat(updatedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001003"); + assertThat(updatedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001001"); + assertThat(updatedProblemSet.getProblemIds().get(2).getId()).isEqualTo("24052001002"); + } + + @Test + @Rollback(true) + void 문항세트_업데이트_테스트() { + // given + Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); + + // when + ProblemSetUpdateRequest updateRequest = new ProblemSetUpdateRequest( + "업데이트된 문항세트", + List.of("24052001002", "24052001003") + ); + problemSetUpdateService.updateProblemSet(problemSetId, updateRequest); + + // then + ProblemSet updatedProblemSet = problemSetRepository.findById(problemSetId) + .orElseThrow(() -> new IllegalArgumentException("문항세트를 찾을 수 없습니다.")); + + assertThat(updatedProblemSet.getName()).isEqualTo("업데이트된 문항세트"); + assertThat(updatedProblemSet.getProblemIds()).hasSize(2); + assertThat(updatedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001002"); + assertThat(updatedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001003"); + } + + @Test + @Rollback(true) + void 문항세트_컨펌_토글_테스트() { + // given + Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); + + // when + ProblemSetConfirmStatus firstToggleStatus = problemSetUpdateService.toggleConfirmProblemSet(problemSetId); // CONFIRMED + ProblemSetConfirmStatus secondToggleStatus = problemSetUpdateService.toggleConfirmProblemSet(problemSetId); // NOT_CONFIRMED + + // then + assertThat(firstToggleStatus).isEqualTo(ProblemSetConfirmStatus.CONFIRMED); // 첫 번째 호출 후 컨펌 상태 확인 + assertThat(secondToggleStatus).isEqualTo(ProblemSetConfirmStatus.NOT_CONFIRMED); // 두 번째 호출 후 비컨펌 상태 확인 + } + + @Test + @Rollback(true) + void 유효하지_않은_문항이_포함된_문항세트_컨펌_실패_테스트() { + // given + Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); + + // 유효하지 않은 문항을 포함하도록 설정 (문항 ID가 존재하지 않거나 필수 필드가 누락된 경우) + ProblemSetUpdateRequest invalidUpdateRequest = new ProblemSetUpdateRequest( + "유효하지 않은 문항세트", + List.of("24052001001", "24052001004") + ); + problemSetUpdateService.updateProblemSet(problemSetId, invalidUpdateRequest); + + // when & then + assertThatThrownBy(() -> problemSetUpdateService.toggleConfirmProblemSet(problemSetId)) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/resources/insert-problemset.sql b/src/test/resources/insert-problemset.sql new file mode 100644 index 0000000..eefeac1 --- /dev/null +++ b/src/test/resources/insert-problemset.sql @@ -0,0 +1,40 @@ +DELETE FROM practice_test_tag; +DELETE FROM concept_tag; +DELETE FROM problem; +DELETE FROM practice_test; -- practice_test 데이터도 삭제 + +-- practice_test 데이터 삽입 +INSERT INTO practice_test (practice_test_id, publication_year, subject, name, provider, round, average_solving_time, solves_count, view_count) +VALUES (1, 2024, '고1', '2024 5월 고1 모의고사', '교육청', '1', 60, 100, 1000); + +-- practice-test-tag 데이터 삽입 +INSERT INTO practice_test_tag (id, test_year, test_month, subject) +VALUES (1, 2024, 5, '고1'); + +-- concept-tag 데이터 삽입 +INSERT INTO concept_tag (concept_tag_id, name) +VALUES (1, '미적분'), + (2, '기하'), + (3, '확통'); + +-- problem 데이터 삽입 +INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, + main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, + is_published, is_variation) +VALUES ('24052001001', 1, 1, '1', '기존 문제 설명', + 'mainProblem.png', 'mainAnalysis.png', 'readingTip.png', 'seniorTip.png', 'prescription.png', + false, false), + ('24052001002', 1, 2, '2', '문제 2 설명', + 'mainProblem2.png', 'mainAnalysis2.png', 'readingTip2.png', 'seniorTip2.png', 'prescription2.png', + false, false), + ('24052001003', 1, 3, '3', '문제 3 설명', + 'mainProblem3.png', 'mainAnalysis3.png', 'readingTip3.png', 'seniorTip3.png', 'prescription3.png', + false, false); + +-- 유효하지 않은 문제 데이터 삽입 (answer와 mainProblemImageUrl이 NULL) +INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, + main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, + is_published, is_variation) +VALUES ('24052001004', 1, 4, NULL, '유효하지 않은 문제 설명', + NULL, 'mainAnalysis4.png', 'readingTip4.png', 'seniorTip4.png', 'prescription4.png', + false, false); \ No newline at end of file From fbf55201a80a003d8d217ddffbd44c5827d4e80b Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 2 Feb 2025 19:15:48 +0900 Subject: [PATCH 048/115] =?UTF-8?q?[fix/#31]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=AC=B8=ED=95=AD=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20null?= =?UTF-8?q?=EA=B0=92=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/insert-problemset.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/resources/insert-problemset.sql b/src/test/resources/insert-problemset.sql index eefeac1..64e7fe8 100644 --- a/src/test/resources/insert-problemset.sql +++ b/src/test/resources/insert-problemset.sql @@ -35,6 +35,6 @@ VALUES ('24052001001', 1, 1, '1', '기존 문제 설명', INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, is_published, is_variation) -VALUES ('24052001004', 1, 4, NULL, '유효하지 않은 문제 설명', - NULL, 'mainAnalysis4.png', 'readingTip4.png', 'seniorTip4.png', 'prescription4.png', +VALUES ('24052001004', 1, 4, '', '유효하지 않은 문제 설명', + '', 'mainAnalysis4.png', 'readingTip4.png', 'seniorTip4.png', 'prescription4.png', false, false); \ No newline at end of file From ffa7cf32e1808386441c8ab9e05ab13ad5c957e3 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 2 Feb 2025 19:20:20 +0900 Subject: [PATCH 049/115] =?UTF-8?q?[fix/#31]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=8B=9C=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/insert-problemset.sql | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/resources/insert-problemset.sql b/src/test/resources/insert-problemset.sql index 64e7fe8..a68b35b 100644 --- a/src/test/resources/insert-problemset.sql +++ b/src/test/resources/insert-problemset.sql @@ -1,7 +1,9 @@ +DELETE FROM problem_set_problems; +DELETE FROM problem_set; +DELETE FROM problem; DELETE FROM practice_test_tag; DELETE FROM concept_tag; -DELETE FROM problem; -DELETE FROM practice_test; -- practice_test 데이터도 삭제 +DELETE FROM practice_test; -- practice_test 데이터 삽입 INSERT INTO practice_test (practice_test_id, publication_year, subject, name, provider, round, average_solving_time, solves_count, view_count) From d07fbe4e1432ebcefe8944915dd3b68ee4de9fa4 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 2 Feb 2025 19:28:58 +0900 Subject: [PATCH 050/115] =?UTF-8?q?[feat/#31]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=BB=A8=ED=8E=8C=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=94=EA=BE=B8=EB=8A=94=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EB=A7=8C=20=EB=AC=B8=ED=95=AD=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problemset/service/ProblemSetUpdateService.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java index a7c95f5..e606bf0 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java @@ -52,12 +52,14 @@ public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) public ProblemSetConfirmStatus toggleConfirmProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); - // 문항 유효성 검사 - for (ProblemId problemId : problemSet.getProblemIds()) { - Problem problem = problemRepository.findByIdElseThrow(problemId); + if(problemSet.getConfirmStatus() == ProblemSetConfirmStatus.NOT_CONFIRMED){ + // 문항 유효성 검사 + for (ProblemId problemId : problemSet.getProblemIds()) { + Problem problem = problemRepository.findByIdElseThrow(problemId); - if (!problem.isValid()) { - throw new InvalidValueException(ErrorCode.INVALID_CONFIRM_PROBLEM); + if (!problem.isValid()) { + throw new InvalidValueException(ErrorCode.INVALID_CONFIRM_PROBLEM); + } } } From b604b25d01bcde4fe82e4da4c4e388d9cb7ea615 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 2 Feb 2025 19:33:43 +0900 Subject: [PATCH 051/115] =?UTF-8?q?[fix/#31]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=ED=9B=84=20=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moplus_server/domain/problemset/ProblemSetServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index cf36fe8..856cc15 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -50,7 +50,7 @@ void setUp() { } @Test - @Rollback(false) + @Rollback(true) void 문항세트_생성_테스트() { // when Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); From c67c13f31340891acdaae8e635acfca3962d54b4 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 2 Feb 2025 20:20:31 +0900 Subject: [PATCH 052/115] =?UTF-8?q?[fix/#31]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=8B=9C=20=EC=97=B0=EA=B4=80=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/insert-problemset.sql | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/test/resources/insert-problemset.sql b/src/test/resources/insert-problemset.sql index a68b35b..6e50eca 100644 --- a/src/test/resources/insert-problemset.sql +++ b/src/test/resources/insert-problemset.sql @@ -1,23 +1,11 @@ +DELETE FROM problem_concept; +DELETE +FROM child_problem_concept; +DELETE +FROM child_problem; DELETE FROM problem_set_problems; DELETE FROM problem_set; DELETE FROM problem; -DELETE FROM practice_test_tag; -DELETE FROM concept_tag; -DELETE FROM practice_test; - --- practice_test 데이터 삽입 -INSERT INTO practice_test (practice_test_id, publication_year, subject, name, provider, round, average_solving_time, solves_count, view_count) -VALUES (1, 2024, '고1', '2024 5월 고1 모의고사', '교육청', '1', 60, 100, 1000); - --- practice-test-tag 데이터 삽입 -INSERT INTO practice_test_tag (id, test_year, test_month, subject) -VALUES (1, 2024, 5, '고1'); - --- concept-tag 데이터 삽입 -INSERT INTO concept_tag (concept_tag_id, name) -VALUES (1, '미적분'), - (2, '기하'), - (3, '확통'); -- problem 데이터 삽입 INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, From dc6ee2a873a1d43168f5e60bb4f002a83c5d2fa8 Mon Sep 17 00:00:00 2001 From: HongGit Date: Mon, 3 Feb 2025 22:29:59 +0900 Subject: [PATCH 053/115] =?UTF-8?q?[feat/#31]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=ED=86=A0=EA=B8=80=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problemset/domain/ProblemSet.java | 17 ++++++++++++----- .../domain/ProblemSetConfirmStatus.java | 7 +++++-- .../service/ProblemSetUpdateService.java | 18 ++++++------------ .../problemset/ProblemSetServiceTest.java | 5 ----- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index f5f1bf2..4b2e7d9 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -1,7 +1,10 @@ package com.moplus.moplus_server.domain.problemset.domain; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.global.common.BaseEntity; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; @@ -58,12 +61,16 @@ public void deleteProblemSet() { this.isDeleted = true; } - public void toggleConfirm() { - if (this.confirmStatus == ProblemSetConfirmStatus.CONFIRMED) { - this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED; - } else { - this.confirmStatus = ProblemSetConfirmStatus.CONFIRMED; + public void toggleConfirm(List problems) { + if(this.confirmStatus == ProblemSetConfirmStatus.NOT_CONFIRMED){ + // 문항 유효성 검사 + for (Problem problem : problems) { + if (!problem.isValid()) { + throw new InvalidValueException(ErrorCode.INVALID_CONFIRM_PROBLEM); + } + } } + this.confirmStatus = this.confirmStatus.toggle(); } public void updateProblemSet(String name, List newProblems) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSetConfirmStatus.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSetConfirmStatus.java index 7dec452..e108d26 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSetConfirmStatus.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSetConfirmStatus.java @@ -2,5 +2,8 @@ public enum ProblemSetConfirmStatus { CONFIRMED, - NOT_CONFIRMED -} + NOT_CONFIRMED; + public ProblemSetConfirmStatus toggle() { + return this == CONFIRMED ? NOT_CONFIRMED : CONFIRMED; + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java index e606bf0..fd42a0f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java @@ -10,6 +10,7 @@ import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -51,19 +52,12 @@ public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) @Transactional public ProblemSetConfirmStatus toggleConfirmProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); - - if(problemSet.getConfirmStatus() == ProblemSetConfirmStatus.NOT_CONFIRMED){ - // 문항 유효성 검사 - for (ProblemId problemId : problemSet.getProblemIds()) { - Problem problem = problemRepository.findByIdElseThrow(problemId); - - if (!problem.isValid()) { - throw new InvalidValueException(ErrorCode.INVALID_CONFIRM_PROBLEM); - } - } + List problems = new ArrayList<>(); + for (ProblemId problemId : problemSet.getProblemIds()) { + Problem problem = problemRepository.findByIdElseThrow(problemId); + problems.add(problem); } - - problemSet.toggleConfirm(); + problemSet.toggleConfirm(problems); return problemSet.getConfirmStatus(); } } diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index 856cc15..e7c95ca 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -50,7 +50,6 @@ void setUp() { } @Test - @Rollback(true) void 문항세트_생성_테스트() { // when Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); @@ -68,7 +67,6 @@ void setUp() { } @Test - @Rollback(true) void 문항세트_문항순서_변경_테스트() { // given Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); @@ -89,7 +87,6 @@ void setUp() { } @Test - @Rollback(true) void 문항세트_업데이트_테스트() { // given Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); @@ -112,7 +109,6 @@ void setUp() { } @Test - @Rollback(true) void 문항세트_컨펌_토글_테스트() { // given Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); @@ -127,7 +123,6 @@ void setUp() { } @Test - @Rollback(true) void 유효하지_않은_문항이_포함된_문항세트_컨펌_실패_테스트() { // given Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); From f603bfb40d5d435d5571ef3f9d17b9584398bf51 Mon Sep 17 00:00:00 2001 From: HongGit Date: Thu, 6 Feb 2025 16:00:15 +0900 Subject: [PATCH 054/115] =?UTF-8?q?[chore/#33]=20local=20env=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 227c6ad..7c43360 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ .env +local.env ### STS ### .apt_generated From d520a95d12ebc991e8e7b7e476c3df6df0578a5d Mon Sep 17 00:00:00 2001 From: HongGit Date: Thu, 6 Feb 2025 16:01:15 +0900 Subject: [PATCH 055/115] =?UTF-8?q?[feat/#33]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ConceptTagRepository.java | 9 ++ .../controller/ProblemSetController.java | 18 ++- .../dto/response/ProblemSetGetResponse.java | 24 +++ .../dto/response/ProblemSummaryResponse.java | 29 ++++ .../service/ProblemSetGetService.java | 45 ++++++ .../problemset/ProblemSetServiceTest.java | 1 - .../service/ProblemSetGetServiceTest.java | 151 ++++++++++++++++++ 7 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java create mode 100644 src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java b/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java index 682a2f3..4f2eafc 100644 --- a/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java @@ -18,4 +18,13 @@ default void existsByIdElseThrow(Set ids) { throw new NotFoundException(ErrorCode.CONCEPT_TAG_NOT_FOUND_IN_LIST); } } + + + default List findByIdInElseThrow(Set ids) { + List conceptTags = findAllById(ids); + if (conceptTags.size() != ids.size()) { + throw new NotFoundException(ErrorCode.CONCEPT_TAG_NOT_FOUND_IN_LIST); + } + return conceptTags; + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java index d101b45..1abe73f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -4,13 +4,16 @@ import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; +import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetGetResponse; import com.moplus.moplus_server.domain.problemset.service.ProblemSetDeleteService; +import com.moplus.moplus_server.domain.problemset.service.ProblemSetGetService; import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService; import com.moplus.moplus_server.domain.problemset.service.ProblemSetUpdateService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -26,6 +29,7 @@ public class ProblemSetController { private final ProblemSetSaveService problemSetSaveService; private final ProblemSetUpdateService problemSetUpdateService; private final ProblemSetDeleteService problemSetDeleteService; + private final ProblemSetGetService problemSetGetService; @PostMapping("") @Operation(summary = "문항세트 생성", description = "문항세트를 생성합니다. 문항은 요청 순서대로 저장합니다.") @@ -65,7 +69,17 @@ public ResponseEntity deleteProblemSet( @PutMapping("/{problemSetId}/confirm") @Operation(summary = "문항세트 컨펌 토글", description = "문항세트의 컨펌 상태를 토글합니다.") - public ResponseEntity toggleConfirmProblemSet(@PathVariable Long problemSetId) { + public ResponseEntity toggleConfirmProblemSet( + @PathVariable Long problemSetId + ) { return ResponseEntity.ok(problemSetUpdateService.toggleConfirmProblemSet(problemSetId)); } -} + + @GetMapping("/{problemSetId}") + @Operation(summary = "문항세트 개별 조회", description = "문항세트를 조회합니다.") + public ResponseEntity getProblemSet( + @PathVariable Long problemSetId + ) { + return ResponseEntity.ok(problemSetGetService.getProblemSet(problemSetId)); + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java new file mode 100644 index 0000000..df68bc2 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java @@ -0,0 +1,24 @@ +package com.moplus.moplus_server.domain.problemset.dto.response; + +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; +import java.util.List; +import lombok.Builder; + +@Builder +public record ProblemSetGetResponse( + Long id, + String name, + ProblemSetConfirmStatus confirmStatus, + List problemSummaries +) { + public static ProblemSetGetResponse of(ProblemSet problemSet, List problemSummaries) { + + return ProblemSetGetResponse.builder() + .id(problemSet.getId()) + .name(problemSet.getName()) + .confirmStatus(problemSet.getConfirmStatus()) + .problemSummaries(problemSummaries) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java new file mode 100644 index 0000000..78251b2 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -0,0 +1,29 @@ +package com.moplus.moplus_server.domain.problemset.dto.response; + +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import java.util.List; +import lombok.Builder; + +@Builder +public record ProblemSummaryResponse( + String problemId, + int number, + String practiceTestName, + String comment, + String mainProblemImageUrl, + List tagNames, + int sequence +) { + public static ProblemSummaryResponse of(Problem problem, int sequence, String practiceTestName, List tagNames) { + + return ProblemSummaryResponse.builder() + .problemId(problem.getId().toString()) + .number(problem.getNumber()) + .comment(problem.getComment()) + .mainProblemImageUrl(problem.getMainProblemImageUrl()) + .sequence(sequence) + .practiceTestName(practiceTestName) + .tagNames(tagNames) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java new file mode 100644 index 0000000..d704b7b --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java @@ -0,0 +1,45 @@ +package com.moplus.moplus_server.domain.problemset.service; + +import com.moplus.moplus_server.domain.concept.domain.ConceptTag; +import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetGetResponse; +import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSummaryResponse; +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProblemSetGetService { + + private final ProblemSetRepository problemSetRepository; + private final ProblemRepository problemRepository; + private final PracticeTestTagRepository practiceTestTagRepository; + private final ConceptTagRepository conceptTagRepository; + + @Transactional(readOnly = true) + public ProblemSetGetResponse getProblemSet(Long problemSetId) { + + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + + List problemSummaries = new ArrayList<>(); + for (int i = 0; i < problemSet.getProblemIds().size(); i++) { + Problem problem = problemRepository.findByIdElseThrow(problemSet.getProblemIds().get(i)); + PracticeTestTag practiceTestTag = practiceTestTagRepository.findByIdElseThrow(problem.getPracticeTestId()); + List tagNames = conceptTagRepository.findByIdInElseThrow(problem.getConceptTagIds()) + .stream() + .map(ConceptTag::getName) + .toList(); + problemSummaries.add(ProblemSummaryResponse.of(problem, i, practiceTestTag.getName(), tagNames)); + } + return ProblemSetGetResponse.of(problemSet, problemSummaries); + } +} diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index e7c95ca..83c9388 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -18,7 +18,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.transaction.annotation.Transactional; diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java new file mode 100644 index 0000000..3bca81f --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java @@ -0,0 +1,151 @@ +package com.moplus.moplus_server.domain.problemset.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; +import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetGetResponse; +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.global.error.exception.NotFoundException; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@ActiveProfiles("h2test") +@Sql({"/practice-test-tag.sql", "/concept-tag.sql"}) +@SpringBootTest +public class ProblemSetGetServiceTest { + + @Autowired + private ProblemSetGetService problemSetGetService; + + @Autowired + private ProblemSetRepository problemSetRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private PracticeTestTagRepository practiceTestTagRepository; + + private ProblemSet savedProblemSet; + private Problem savedProblem; + + @BeforeEach + void setUp() { + // 문제 생성 요청 데이터 준비 + ProblemPostRequest problemPostRequest = new ProblemPostRequest( + Set.of(1L, 2L), + 1L, + 1, + "1", + "문제 설명", + "mainProblem.png", + "mainAnalysis.png", + "readingTip.png", + "seniorTip.png", + "prescription.png", + List.of() + ); + + // 문제 저장 + ProblemId createdProblemId = problemRepository.save( + new Problem( + new ProblemId("24052001001"), + practiceTestTagRepository.findByIdElseThrow(1L), + 1, + "1", + "문제 설명", + "mainProblem.png", + "mainAnalysis.png", + "readingTip.png", + "seniorTip.png", + "prescription.png", + Set.of(1L, 2L) + ) + ).getId(); + + savedProblem = problemRepository.findByIdElseThrow(createdProblemId); + + // 문항세트 저장 + savedProblemSet = problemSetRepository.save( + new ProblemSet("테스트 문항세트", List.of(savedProblem.getId())) + ); + } + + @Test + void 문항세트_조회_성공_테스트() { + // when + ProblemSetGetResponse response = problemSetGetService.getProblemSet(savedProblemSet.getId()); + + // then + assertThat(response).isNotNull(); + assertThat(response.name()).isEqualTo("테스트 문항세트"); + assertThat(response.problemSummaries()).hasSize(1); + assertThat(response.problemSummaries().get(0).problemId()).isEqualTo(savedProblem.getId().toString()); + assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); + assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); + } + + @Test + void 존재하지_않는_문항세트_조회_실패_테스트() { + // when & then + assertThatThrownBy(() -> problemSetGetService.getProblemSet(999L)) + .isInstanceOf(NotFoundException.class) + .hasMessageContaining("해당 문항세트를 찾을 수 없습니다"); + } + + @Test + void 문항세트_조회_성공_테스트_여러개() { + // given: 여러 개의 문제를 저장 + Problem savedProblem2 = problemRepository.save( + new Problem( + new ProblemId("24052001002"), + practiceTestTagRepository.findByIdElseThrow(1L), + 2, + "2", + "문제 설명2", + "mainProblem2.png", + "mainAnalysis2.png", + "readingTip2.png", + "seniorTip2.png", + "prescription2.png", + Set.of(3L, 4L) + ) + ); + + ProblemSet multipleProblemSet = problemSetRepository.save( + new ProblemSet("여러 문항 테스트 문항세트", List.of(savedProblem.getId(), savedProblem2.getId())) + ); + + // when + ProblemSetGetResponse response = problemSetGetService.getProblemSet(multipleProblemSet.getId()); + + // then + assertThat(response).isNotNull(); + assertThat(response.name()).isEqualTo("여러 문항 테스트 문항세트"); + assertThat(response.problemSummaries()).hasSize(2); + + // 첫 번째 문제 검증 + assertThat(response.problemSummaries().get(0).problemId()).isEqualTo(savedProblem.getId().toString()); + assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); + assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); + + // 두 번째 문제 검증 + assertThat(response.problemSummaries().get(1).problemId()).isEqualTo(savedProblem2.getId().toString()); + assertThat(response.problemSummaries().get(1).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); + assertThat(response.problemSummaries().get(1).tagNames()).contains("삼각함수 개념", "행렬 개념"); + } +} \ No newline at end of file From ba49c981c4535f25cd71d45dc476590ac8790466 Mon Sep 17 00:00:00 2001 From: HongGit Date: Thu, 6 Feb 2025 18:40:34 +0900 Subject: [PATCH 056/115] =?UTF-8?q?[fix/#33]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20title=EB=A1=9C=20=ED=95=84=EB=93=9C=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problemset/controller/ProblemSetSearchController.java | 2 ++ .../domain/problemset/domain/ProblemSet.java | 8 ++++---- .../problemset/dto/request/ProblemSetPostRequest.java | 4 ++-- .../problemset/dto/request/ProblemSetUpdateRequest.java | 2 +- .../problemset/dto/response/ProblemSetGetResponse.java | 4 ++-- .../problemset/service/ProblemSetUpdateService.java | 4 +--- 6 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java new file mode 100644 index 0000000..7f1b865 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java @@ -0,0 +1,2 @@ +package com.moplus.moplus_server.domain.problemset.controller;public class ProblemSetSearchController { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index 4b2e7d9..775ba79 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -33,7 +33,7 @@ public class ProblemSet extends BaseEntity { @Column(name = "problem_set_id") Long id; - private String name; + private String title; private boolean isDeleted; @Enumerated(EnumType.STRING) @@ -46,8 +46,8 @@ public class ProblemSet extends BaseEntity { private List problemIds = new ArrayList<>(); @Builder - public ProblemSet(String name, List problemIds) { - this.name = name; + public ProblemSet(String title, List problemIds) { + this.title = title; this.isDeleted = false; this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED; this.problemIds = problemIds; @@ -74,7 +74,7 @@ public void toggleConfirm(List problems) { } public void updateProblemSet(String name, List newProblems) { - this.name = name; + this.title = title; this.problemIds = newProblems; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java index 0401915..eb50f4d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java @@ -5,12 +5,12 @@ import java.util.List; public record ProblemSetPostRequest( - String problemSetName, + String problemSetTitle, List problems ) { public ProblemSet toEntity(List problemIdList) { return ProblemSet.builder() - .name(this.problemSetName) + .title(this.problemSetTitle) .problemIds(problemIdList) .build(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java index a0e4745..6752f0e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java @@ -3,7 +3,7 @@ import java.util.List; public record ProblemSetUpdateRequest( - String problemSetName, + String problemSetTitle, List problems ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java index df68bc2..c6fa190 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java @@ -8,7 +8,7 @@ @Builder public record ProblemSetGetResponse( Long id, - String name, + String title, ProblemSetConfirmStatus confirmStatus, List problemSummaries ) { @@ -16,7 +16,7 @@ public static ProblemSetGetResponse of(ProblemSet problemSet, List Date: Fri, 7 Feb 2025 12:23:22 +0900 Subject: [PATCH 057/115] =?UTF-8?q?[feat/#33]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EA=B2=80=EC=83=89=20api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProblemSetSearchController.java | 35 +++++- .../response/ProblemSetSearchGetResponse.java | 18 +++ .../response/ProblemThumbnailResponse.java | 17 +++ .../ProblemSetSearchRepositoryCustom.java | 78 ++++++++++++ .../problemset/ProblemSetServiceTest.java | 6 +- .../ProblemSetSearchRepositoryCustomTest.java | 117 ++++++++++++++++++ .../service/ProblemSetGetServiceTest.java | 4 +- src/test/resources/insert-problem-set.sql | 11 ++ src/test/resources/insert-problem.sql | 14 +-- ...ert-problemset.sql => insert-problem2.sql} | 0 10 files changed, 287 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java create mode 100644 src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java create mode 100644 src/test/resources/insert-problem-set.sql rename src/test/resources/{insert-problemset.sql => insert-problem2.sql} (100%) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java index 7f1b865..405a66f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java @@ -1,2 +1,35 @@ -package com.moplus.moplus_server.domain.problemset.controller;public class ProblemSetSearchController { +package com.moplus.moplus_server.domain.problemset.controller; + + +import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetSearchRepositoryCustom; +import io.swagger.v3.oas.annotations.Operation; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/problemSet") +@RequiredArgsConstructor +public class ProblemSetSearchController { + + private final ProblemSetSearchRepositoryCustom problemSetSearchRepository; + + @GetMapping("/search") + @Operation( + summary = "문항세트 검색", + description = "문항세트 타이틀, 문항세트 내 포함된 개념태그, 문항세트 내 포함된 문항 타이틀로 검색합니다." + ) + public ResponseEntity> search( + @RequestParam(value = "problemSetTitle", required = false) String problemSetTitle, + @RequestParam(value = "problemTitle", required = false) String problemTitle, + @RequestParam(value = "conceptTagNames", required = false) List conceptTagNames + ) { + List problemSets = problemSetSearchRepository.search(problemSetTitle, problemTitle, conceptTagNames); + return ResponseEntity.ok(problemSets); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java new file mode 100644 index 0000000..392783c --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java @@ -0,0 +1,18 @@ +package com.moplus.moplus_server.domain.problemset.dto.response; + +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ProblemSetSearchGetResponse { + private String problemSetTitle; + private List problemThumbnailResponses; + + public ProblemSetSearchGetResponse(String problemSetTitle, + List problemThumbnailResponses) { + this.problemSetTitle = problemSetTitle; + this.problemThumbnailResponses = problemThumbnailResponses; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java new file mode 100644 index 0000000..b4b4b94 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java @@ -0,0 +1,17 @@ +package com.moplus.moplus_server.domain.problemset.dto.response; + +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ProblemThumbnailResponse { + private String mainProblemImageUrl; + private List tagNames; + + public ProblemThumbnailResponse(String mainProblemImageUrl, List tagNames) { + this.mainProblemImageUrl = mainProblemImageUrl; + this.tagNames = tagNames; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java new file mode 100644 index 0000000..31720c7 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -0,0 +1,78 @@ +package com.moplus.moplus_server.domain.problemset.repository; + +import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag; +import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; +import static com.moplus.moplus_server.domain.problemset.domain.QProblemSet.problemSet; + +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; +import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; +import com.moplus.moplus_server.domain.problemset.domain.QProblemSet; +import com.moplus.moplus_server.domain.problem.domain.problem.QProblem; +import com.moplus.moplus_server.domain.concept.domain.QConceptTag; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class ProblemSetSearchRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + public List search(String problemSetTitle, String problemTitle, List conceptTagNames) { + + return queryFactory + .select(problemSet) + .from(problemSet) + .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) + .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) + .where( + problemSetTitleContains(problemSetTitle), + problemTitleContains(problemTitle), + conceptTagNamesIn(conceptTagNames) + ) + .fetch() + .stream() + .map(ps -> new ProblemSetSearchGetResponse( + ps.getTitle(), + ps.getProblemIds().stream() + .map(pid -> { + Problem p = queryFactory + .select(problem) + .from(problem) + .where(problem.id.eq(pid)) + .fetchOne(); + return new ProblemThumbnailResponse( + p != null ? p.getMainProblemImageUrl() : null, + p != null ? p.getConceptTagIds().stream() + .map(tagId -> queryFactory + .select(conceptTag.name) + .from(conceptTag) + .where(conceptTag.id.eq(tagId)) + .fetchOne()) + .collect(Collectors.toList()) + : List.of() + ); + }) + .collect(Collectors.toList()) + )) + .collect(Collectors.toList()); + } + + private BooleanExpression problemSetTitleContains(String problemSetTitle) { + return problemSetTitle != null ? QProblemSet.problemSet.title.contains(problemSetTitle) : null; + } + + private BooleanExpression problemTitleContains(String problemTitle) { + return problemTitle != null ? QProblem.problem.comment.contains(problemTitle) : null; + } + + private BooleanExpression conceptTagNamesIn(List conceptTagNames) { + return (conceptTagNames != null && !conceptTagNames.isEmpty()) ? + QConceptTag.conceptTag.name.in(conceptTagNames) : null; + } +} \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index 83c9388..bf89640 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -24,7 +24,7 @@ @Transactional @ActiveProfiles("h2test") -@Sql({"/insert-problemset.sql"}) +@Sql({"/insert-problem2.sql"}) @SpringBootTest public class ProblemSetServiceTest { @@ -58,7 +58,7 @@ void setUp() { .orElseThrow(() -> new IllegalArgumentException("문항세트를 찾을 수 없습니다.")); assertThat(savedProblemSet).isNotNull(); - assertThat(savedProblemSet.getName()).isEqualTo("초기 문항세트"); + assertThat(savedProblemSet.getTitle()).isEqualTo("초기 문항세트"); assertThat(savedProblemSet.getProblemIds()).hasSize(3); assertThat(savedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001001"); assertThat(savedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001002"); @@ -101,7 +101,7 @@ void setUp() { ProblemSet updatedProblemSet = problemSetRepository.findById(problemSetId) .orElseThrow(() -> new IllegalArgumentException("문항세트를 찾을 수 없습니다.")); - assertThat(updatedProblemSet.getName()).isEqualTo("업데이트된 문항세트"); + assertThat(updatedProblemSet.getTitle()).isEqualTo("업데이트된 문항세트"); assertThat(updatedProblemSet.getProblemIds()).hasSize(2); assertThat(updatedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001002"); assertThat(updatedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001003"); diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java new file mode 100644 index 0000000..17be096 --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -0,0 +1,117 @@ +package com.moplus.moplus_server.domain.problemset.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; +import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@SpringBootTest +@ActiveProfiles("h2test") +@Sql({"/practice-test-tag.sql", "/concept-tag.sql", "/insert-problem.sql", "/insert-problem-set.sql"}) +public class ProblemSetSearchRepositoryCustomTest { + + @Autowired + private ProblemSetSearchRepositoryCustom problemSetSearchRepository; + + @Test + void 문항세트_타이틀_일부_포함_검색() { + // when + List result = problemSetSearchRepository.search("고2 모의고사", null, null); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + } + + @Test + void 문항타이틀_포함_검색() { + // when + List result = problemSetSearchRepository.search(null, "코멘트1", null); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + } + + @Test + void 개념태그_하나라도_포함되면_조회() { + // when + List result = problemSetSearchRepository.search(null, null, List.of("미분 개념")); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + } + + @Test + void 모두_적용된_검색() { + // when + List result = problemSetSearchRepository.search("고2", "코멘트1", List.of("미분 개념")); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + } + + @Test + void 아무_조건도_없으면_모든_데이터_조회() { + // when + List result = problemSetSearchRepository.search(null, null, null); + + // then + assertThat(result).hasSize(1); + } + + @Test + void 문항_여러개_문항세트_검색_조회() { + // when + List result = problemSetSearchRepository.search("고2 모의고사", null, null); + + // then + assertThat(result).hasSize(1); + ProblemSetSearchGetResponse response = result.get(0); + assertThat(response.getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + + // ✅ 문항이 2개 존재하는지 확인 + List problems = response.getProblemThumbnailResponses(); + assertThat(problems).hasSize(2); + + // ✅ 문항의 이미지 URL이 올바르게 매핑되었는지 확인 + assertThat(problems.get(0).getMainProblemImageUrl()).isEqualTo("mainProblem.png1"); + assertThat(problems.get(1).getMainProblemImageUrl()).isEqualTo("mainProblem.png2"); + } + + @Test + void 문항태그_조회_테스트() { + // when + List result = problemSetSearchRepository.search("고2 모의고사", null, null); + + // then + assertThat(result).hasSize(1); + ProblemSetSearchGetResponse response = result.get(0); + assertThat(response.getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + + // ✅ 문항별 개념 태그 확인 + List problems = response.getProblemThumbnailResponses(); + assertThat(problems).hasSize(2); + + // ✅ 첫 번째 문제(240520012001)의 개념 태그 확인 + List firstProblemTags = new ArrayList<>(problems.get(0).getTagNames()); + + assertThat(firstProblemTags).containsExactlyInAnyOrder("미분 개념", "적분 개념", "삼각함수 개념"); + + // ✅ 두 번째 문제(240520012002)의 개념 태그 확인 + List secondProblemTags = new ArrayList<>(problems.get(1).getTagNames()); + + assertThat(secondProblemTags).containsExactlyInAnyOrder("미분 개념", "삼각함수 개념"); + } +} \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java index 3bca81f..85e7e40 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java @@ -92,7 +92,7 @@ void setUp() { // then assertThat(response).isNotNull(); - assertThat(response.name()).isEqualTo("테스트 문항세트"); + assertThat(response.title()).isEqualTo("테스트 문항세트"); assertThat(response.problemSummaries()).hasSize(1); assertThat(response.problemSummaries().get(0).problemId()).isEqualTo(savedProblem.getId().toString()); assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); @@ -135,7 +135,7 @@ void setUp() { // then assertThat(response).isNotNull(); - assertThat(response.name()).isEqualTo("여러 문항 테스트 문항세트"); + assertThat(response.title()).isEqualTo("여러 문항 테스트 문항세트"); assertThat(response.problemSummaries()).hasSize(2); // 첫 번째 문제 검증 diff --git a/src/test/resources/insert-problem-set.sql b/src/test/resources/insert-problem-set.sql new file mode 100644 index 0000000..e9c8af1 --- /dev/null +++ b/src/test/resources/insert-problem-set.sql @@ -0,0 +1,11 @@ +DELETE FROM problem_set_problems; +DELETE FROM problem_set; + +-- 문제 세트 추가 +INSERT INTO problem_set (problem_set_id, title, is_deleted, confirm_status) +VALUES (1, '2025년 5월 고2 모의고사 문제 세트', false, 'NOT_CONFIRMED'); + +-- 문제 세트에 포함된 문제 추가 +INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) +VALUES (1, '240520012001', 0), + (1, '240520012002', 1); \ No newline at end of file diff --git a/src/test/resources/insert-problem.sql b/src/test/resources/insert-problem.sql index 530736b..f6ac713 100644 --- a/src/test/resources/insert-problem.sql +++ b/src/test/resources/insert-problem.sql @@ -6,18 +6,18 @@ FROM child_problem; INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, is_published, is_variation) -VALUES ('240520012001', 1, 1, '1', '기존 문제 설명 1', - 'mainProblem.png', 'mainAnalysis.png', 'readingTip.png', 'seniorTip.png', 'prescription.png', +VALUES ('240520012001', 1, 1, '1', '기존 문제 코멘트1', + 'mainProblem.png1', 'mainAnalysis.png1', 'readingTip.png1', 'seniorTip.png1', 'prescription.png1', false, false), - ('240520012002', 1, 1, '1', '기존 문제 설명 2', - 'mainProblem.png', 'mainAnalysis.png', 'readingTip.png', 'seniorTip.png', 'prescription.png', + ('240520012002', 1, 1, '1', '기존 문제 코멘트2', + 'mainProblem.png2', 'mainAnalysis.png2', 'readingTip.png2', 'seniorTip.png2', 'prescription.png2', false, false); --- ✅ 기존 자식 문제(ChildProblem) 삽입 +-- 기존 자식 문제(ChildProblem) 삽입 INSERT INTO child_problem (child_problem_id, problem_id, image_url, problem_type, answer, sequence) VALUES (1, '240520012001', 'child1.png', 'MULTIPLE_CHOICE', '1', 0), (2, '240520012001', 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 0); --- ✅ 문제-컨셉 태그 연결 (기존 문제의 ConceptTag) +-- 문제-컨셉 태그 연결 (기존 문제의 ConceptTag) INSERT INTO problem_concept (problem_id, concept_tag_id) VALUES ('240520012001', 1), ('240520012001', 2), @@ -25,7 +25,7 @@ VALUES ('240520012001', 1), ('240520012002', 1), ('240520012002', 3); --- ✅ 자식 문제-컨셉 태그 연결 +-- 자식 문제-컨셉 태그 연결 INSERT INTO child_problem_concept (child_problem_id, concept_tag_id) VALUES (1, 3), (1, 4), diff --git a/src/test/resources/insert-problemset.sql b/src/test/resources/insert-problem2.sql similarity index 100% rename from src/test/resources/insert-problemset.sql rename to src/test/resources/insert-problem2.sql From 7cfcf710be7ecb9f01de25ff5229f3dcf560a10e Mon Sep 17 00:00:00 2001 From: HongGit Date: Fri, 7 Feb 2025 12:50:41 +0900 Subject: [PATCH 058/115] =?UTF-8?q?[fix/#33]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=80=EC=83=89=EC=8B=9C=20=ED=83=9C=EA=B7=B8?= =?UTF-8?q?=EB=AA=85=20=ED=95=84=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/ProblemThumbnailResponse.java | 5 +- .../ProblemSetSearchRepositoryCustom.java | 69 +++++++------------ .../ProblemSetSearchRepositoryCustomTest.java | 26 ------- 3 files changed, 25 insertions(+), 75 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java index b4b4b94..0848eba 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java @@ -1,6 +1,5 @@ package com.moplus.moplus_server.domain.problemset.dto.response; -import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @@ -8,10 +7,8 @@ @NoArgsConstructor public class ProblemThumbnailResponse { private String mainProblemImageUrl; - private List tagNames; - public ProblemThumbnailResponse(String mainProblemImageUrl, List tagNames) { + public ProblemThumbnailResponse(String mainProblemImageUrl) { this.mainProblemImageUrl = mainProblemImageUrl; - this.tagNames = tagNames; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index 31720c7..dcd9661 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -4,16 +4,13 @@ import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; import static com.moplus.moplus_server.domain.problemset.domain.QProblemSet.problemSet; -import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; -import com.moplus.moplus_server.domain.problemset.domain.QProblemSet; -import com.moplus.moplus_server.domain.problem.domain.problem.QProblem; -import com.moplus.moplus_server.domain.concept.domain.QConceptTag; +import com.querydsl.core.group.GroupBy; +import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -24,55 +21,37 @@ public class ProblemSetSearchRepositoryCustom { private final JPAQueryFactory queryFactory; public List search(String problemSetTitle, String problemTitle, List conceptTagNames) { - return queryFactory - .select(problemSet) .from(problemSet) - .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) - .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) + .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 + .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 .where( - problemSetTitleContains(problemSetTitle), - problemTitleContains(problemTitle), - conceptTagNamesIn(conceptTagNames) + containsProblemSetTitle(problemSetTitle), + containsProblemTitle(problemTitle), + containsConceptTagNames(conceptTagNames) ) - .fetch() - .stream() - .map(ps -> new ProblemSetSearchGetResponse( - ps.getTitle(), - ps.getProblemIds().stream() - .map(pid -> { - Problem p = queryFactory - .select(problem) - .from(problem) - .where(problem.id.eq(pid)) - .fetchOne(); - return new ProblemThumbnailResponse( - p != null ? p.getMainProblemImageUrl() : null, - p != null ? p.getConceptTagIds().stream() - .map(tagId -> queryFactory - .select(conceptTag.name) - .from(conceptTag) - .where(conceptTag.id.eq(tagId)) - .fetchOne()) - .collect(Collectors.toList()) - : List.of() - ); - }) - .collect(Collectors.toList()) - )) - .collect(Collectors.toList()); + .distinct() + .transform(GroupBy.groupBy(problemSet.id).list( + Projections.constructor(ProblemSetSearchGetResponse.class, + problemSet.title, + GroupBy.list( + Projections.constructor(ProblemThumbnailResponse.class, + problem.mainProblemImageUrl + ) + ) + ) + )); } - private BooleanExpression problemSetTitleContains(String problemSetTitle) { - return problemSetTitle != null ? QProblemSet.problemSet.title.contains(problemSetTitle) : null; + private BooleanExpression containsProblemSetTitle(String problemSetTitle) { + return (problemSetTitle == null || problemSetTitle.isEmpty()) ? null : problemSet.title.containsIgnoreCase(problemSetTitle); } - private BooleanExpression problemTitleContains(String problemTitle) { - return problemTitle != null ? QProblem.problem.comment.contains(problemTitle) : null; + private BooleanExpression containsProblemTitle(String problemTitle) { + return (problemTitle == null || problemTitle.isEmpty()) ? null : problem.comment.containsIgnoreCase(problemTitle); } - private BooleanExpression conceptTagNamesIn(List conceptTagNames) { - return (conceptTagNames != null && !conceptTagNames.isEmpty()) ? - QConceptTag.conceptTag.name.in(conceptTagNames) : null; + private BooleanExpression containsConceptTagNames(List conceptTagNames) { + return (conceptTagNames == null || conceptTagNames.isEmpty()) ? null : conceptTag.name.in(conceptTagNames); } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index 17be096..e394d6d 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -4,7 +4,6 @@ import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; -import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -89,29 +88,4 @@ public class ProblemSetSearchRepositoryCustomTest { assertThat(problems.get(0).getMainProblemImageUrl()).isEqualTo("mainProblem.png1"); assertThat(problems.get(1).getMainProblemImageUrl()).isEqualTo("mainProblem.png2"); } - - @Test - void 문항태그_조회_테스트() { - // when - List result = problemSetSearchRepository.search("고2 모의고사", null, null); - - // then - assertThat(result).hasSize(1); - ProblemSetSearchGetResponse response = result.get(0); - assertThat(response.getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); - - // ✅ 문항별 개념 태그 확인 - List problems = response.getProblemThumbnailResponses(); - assertThat(problems).hasSize(2); - - // ✅ 첫 번째 문제(240520012001)의 개념 태그 확인 - List firstProblemTags = new ArrayList<>(problems.get(0).getTagNames()); - - assertThat(firstProblemTags).containsExactlyInAnyOrder("미분 개념", "적분 개념", "삼각함수 개념"); - - // ✅ 두 번째 문제(240520012002)의 개념 태그 확인 - List secondProblemTags = new ArrayList<>(problems.get(1).getTagNames()); - - assertThat(secondProblemTags).containsExactlyInAnyOrder("미분 개념", "삼각함수 개념"); - } } \ No newline at end of file From 685cd61ca5bae9fbce37b147e9b9f6936b5ff785 Mon Sep 17 00:00:00 2001 From: HongGit Date: Fri, 7 Feb 2025 15:59:11 +0900 Subject: [PATCH 059/115] =?UTF-8?q?[fix/#33]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=88=9C=EC=84=9C=20api=20swagger=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problemset/controller/ProblemSetController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java index 1abe73f..8b13e35 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -9,6 +9,7 @@ import com.moplus.moplus_server.domain.problemset.service.ProblemSetGetService; import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService; import com.moplus.moplus_server.domain.problemset.service.ProblemSetUpdateService; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -39,6 +40,7 @@ public ResponseEntity createProblemSet( return ResponseEntity.ok(problemSetSaveService.createProblemSet(request)); } + @Hidden @PutMapping("/{problemSetId}/sequence") @Operation(summary = "세트 문항순서 변경", description = "문항세트 내의 문항 리스트의 순서를 변경합니다.") public ResponseEntity reorderProblems( From 3d6f968c6c82e06dfbfa706bdddf09dcfdcef851 Mon Sep 17 00:00:00 2001 From: HongGit Date: Fri, 7 Feb 2025 16:38:17 +0900 Subject: [PATCH 060/115] =?UTF-8?q?[fix/#33]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/ProblemSetPostRequest.java | 7 ++- .../service/ProblemSetSaveService.java | 11 +++- .../service/ProblemSetUpdateService.java | 7 +++ .../global/error/exception/ErrorCode.java | 1 + .../problemset/ProblemSetServiceTest.java | 56 +++++++++++++++++++ 5 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java index eb50f4d..a8a614a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java @@ -10,8 +10,13 @@ public record ProblemSetPostRequest( ) { public ProblemSet toEntity(List problemIdList) { return ProblemSet.builder() - .title(this.problemSetTitle) + .title(verifyTitle(this.problemSetTitle)) .problemIds(problemIdList) .build(); } + + // 빈 타이틀 "제목 없음"으로 임시 세팅 + private static String verifyTitle(String title) { + return (title == null || title.trim().isEmpty()) ? "제목 없음" : title; + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java index 3805f6e..c363815 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -3,12 +3,11 @@ import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; -import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; -import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.util.List; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +21,11 @@ public class ProblemSetSaveService { @Transactional public Long createProblemSet(ProblemSetPostRequest request) { + // 빈 문항 유효성 검증 + if (request.problems().isEmpty()) { + throw new InvalidValueException(ErrorCode.EMPTY_PROBLEMS_ERROR); + } + // 문제 ID 리스트를 ProblemId 객체로 변환 List problemIdList = request.problems().stream() .map(ProblemId::new) @@ -35,4 +39,5 @@ public Long createProblemSet(ProblemSetPostRequest request) { return problemSetRepository.save(problemSet).getId(); } + } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java index 9f63f11..92134ac 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java @@ -8,6 +8,8 @@ import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -38,6 +40,11 @@ public void reorderProblems(Long problemSetId, ProblemReorderRequest request) { public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + // 빈 문항 유효성 검증 + if (request.problems().isEmpty()) { + throw new InvalidValueException(ErrorCode.EMPTY_PROBLEMS_ERROR); + } + // 문항 리스트 검증 List problemIdList = request.problems().stream() .map(ProblemId::new) diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 93516b5..747ef27 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -56,6 +56,7 @@ public enum ErrorCode { //문항세트 PROBLEM_SET_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 문항세트를 찾을 수 없습니다"), + EMPTY_PROBLEMS_ERROR(HttpStatus.BAD_REQUEST, "적어도 1개의 문항을 등록해주세요"), ; diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index bf89640..8d02621 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -138,4 +138,60 @@ void setUp() { .isInstanceOf(InvalidValueException.class) .hasMessageContaining(ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage()); } + + @Test + void 빈_문항리스트_문항세트_생성_실패_테스트() { + // given + ProblemSetPostRequest emptyProblemSetRequest = new ProblemSetPostRequest( + "빈 문항세트", + List.of() // 빈 리스트 + ); + + // when & then + assertThatThrownBy(() -> problemSetSaveService.createProblemSet(emptyProblemSetRequest)) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.EMPTY_PROBLEMS_ERROR.getMessage()); + } + + @Test + void 빈_제목_문항세트_생성_테스트() { + // given + ProblemSetPostRequest emptyTitleRequest = new ProblemSetPostRequest( + "", // 빈 문자열 제목 + List.of("24052001001", "24052001002", "24052001003") + ); + + ProblemSetPostRequest nullTitleRequest = new ProblemSetPostRequest( + null, // null 제목 + List.of("24052001001", "24052001002", "24052001003") + ); + + // when + Long emptyTitleProblemSetId = problemSetSaveService.createProblemSet(emptyTitleRequest); + Long nullTitleProblemSetId = problemSetSaveService.createProblemSet(nullTitleRequest); + + // then + ProblemSet emptyTitleSavedProblemSet = problemSetRepository.findByIdElseThrow(emptyTitleProblemSetId); + + ProblemSet nullTitleSavedProblemSet = problemSetRepository.findByIdElseThrow(nullTitleProblemSetId); + + assertThat(emptyTitleSavedProblemSet.getTitle()).isEqualTo("제목 없음"); // 빈 문자열 제목 테스트 + assertThat(nullTitleSavedProblemSet.getTitle()).isEqualTo("제목 없음"); // null 제목 테스트 + } + + @Test + void 문항세트_빈_제목_업데이트_테스트() { + // given + Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); + + ProblemSetUpdateRequest emptyUpdateRequest = new ProblemSetUpdateRequest( + "업데이트된 빈 문항세트", + List.of() + ); + + // when & then + assertThatThrownBy(() -> problemSetUpdateService.updateProblemSet(problemSetId, emptyUpdateRequest)) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.EMPTY_PROBLEMS_ERROR.getMessage()); + } } \ No newline at end of file From 060d5b2cd0359c1b6f35ce1e66502b6266c9db88 Mon Sep 17 00:00:00 2001 From: HongGit Date: Fri, 7 Feb 2025 16:55:25 +0900 Subject: [PATCH 061/115] =?UTF-8?q?[fix/#33]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moplus_server/domain/problemset/domain/ProblemSet.java | 2 +- .../domain/problemset/ProblemSetServiceTest.java | 3 +-- .../repository/ProblemSetSearchRepositoryCustomTest.java | 4 ++-- src/test/resources/insert-problem.sql | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index 775ba79..4f331d6 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -73,7 +73,7 @@ public void toggleConfirm(List problems) { this.confirmStatus = this.confirmStatus.toggle(); } - public void updateProblemSet(String name, List newProblems) { + public void updateProblemSet(String title, List newProblems) { this.title = title; this.problemIds = newProblems; } diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index 8d02621..f9ce288 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -98,8 +98,7 @@ void setUp() { problemSetUpdateService.updateProblemSet(problemSetId, updateRequest); // then - ProblemSet updatedProblemSet = problemSetRepository.findById(problemSetId) - .orElseThrow(() -> new IllegalArgumentException("문항세트를 찾을 수 없습니다.")); + ProblemSet updatedProblemSet = problemSetRepository.findByIdElseThrow(problemSetId); assertThat(updatedProblemSet.getTitle()).isEqualTo("업데이트된 문항세트"); assertThat(updatedProblemSet.getProblemIds()).hasSize(2); diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index e394d6d..9e05157 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -34,7 +34,7 @@ public class ProblemSetSearchRepositoryCustomTest { @Test void 문항타이틀_포함_검색() { // when - List result = problemSetSearchRepository.search(null, "코멘트1", null); + List result = problemSetSearchRepository.search(null, "설명 1", null); // then assertThat(result).hasSize(1); @@ -54,7 +54,7 @@ public class ProblemSetSearchRepositoryCustomTest { @Test void 모두_적용된_검색() { // when - List result = problemSetSearchRepository.search("고2", "코멘트1", List.of("미분 개념")); + List result = problemSetSearchRepository.search("고2", "설명 1", List.of("미분 개념")); // then assertThat(result).hasSize(1); diff --git a/src/test/resources/insert-problem.sql b/src/test/resources/insert-problem.sql index f6ac713..d9ecdfb 100644 --- a/src/test/resources/insert-problem.sql +++ b/src/test/resources/insert-problem.sql @@ -6,10 +6,10 @@ FROM child_problem; INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, is_published, is_variation) -VALUES ('240520012001', 1, 1, '1', '기존 문제 코멘트1', +VALUES ('240520012001', 1, 1, '1', '기존 문제 설명 1', 'mainProblem.png1', 'mainAnalysis.png1', 'readingTip.png1', 'seniorTip.png1', 'prescription.png1', false, false), - ('240520012002', 1, 1, '1', '기존 문제 코멘트2', + ('240520012002', 1, 1, '1', '기존 문제 설명 2', 'mainProblem.png2', 'mainAnalysis.png2', 'readingTip.png2', 'seniorTip.png2', 'prescription.png2', false, false); -- 기존 자식 문제(ChildProblem) 삽입 From f2a110fa6d1111d988213aea5cc4cf87bffa3ef4 Mon Sep 17 00:00:00 2001 From: HongGit Date: Fri, 7 Feb 2025 18:42:55 +0900 Subject: [PATCH 062/115] =?UTF-8?q?[fix/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ConceptTagRepository.java | 2 +- .../domain/problemset/domain/ProblemSet.java | 8 +++--- .../domain/problemset/domain/Title.java | 25 +++++++++++++++++++ .../dto/request/ProblemSetPostRequest.java | 7 +----- .../dto/response/ProblemSetGetResponse.java | 2 +- .../response/ProblemSetSearchGetResponse.java | 5 ++-- .../dto/response/ProblemSummaryResponse.java | 6 ++--- .../ProblemSetSearchRepositoryCustom.java | 4 +-- .../service/ProblemSetGetService.java | 9 ++++--- .../problemset/ProblemSetServiceTest.java | 8 +++--- 10 files changed, 49 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problemset/domain/Title.java diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java b/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java index 4f2eafc..66f71a3 100644 --- a/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/concept/repository/ConceptTagRepository.java @@ -20,7 +20,7 @@ default void existsByIdElseThrow(Set ids) { } - default List findByIdInElseThrow(Set ids) { + default List findAllByIdsElseThrow(Set ids) { List conceptTags = findAllById(ids); if (conceptTags.size() != ids.size()) { throw new NotFoundException(ErrorCode.CONCEPT_TAG_NOT_FOUND_IN_LIST); diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index 4f331d6..cfa041e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -8,6 +8,7 @@ import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -33,7 +34,8 @@ public class ProblemSet extends BaseEntity { @Column(name = "problem_set_id") Long id; - private String title; + @Embedded + private Title title; private boolean isDeleted; @Enumerated(EnumType.STRING) @@ -47,7 +49,7 @@ public class ProblemSet extends BaseEntity { @Builder public ProblemSet(String title, List problemIds) { - this.title = title; + this.title = new Title(title); this.isDeleted = false; this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED; this.problemIds = problemIds; @@ -74,7 +76,7 @@ public void toggleConfirm(List problems) { } public void updateProblemSet(String title, List newProblems) { - this.title = title; + this.title = new Title(title); this.problemIds = newProblems; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/Title.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/Title.java new file mode 100644 index 0000000..aa91e1c --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/Title.java @@ -0,0 +1,25 @@ +package com.moplus.moplus_server.domain.problemset.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@NoArgsConstructor +public class Title { + + private static final String DEFAULT_TITLE = "제목 없음"; + + @Column(name = "title", nullable = false) + private String value; + + public Title(String title) { + this.value = verifyTitle(title); + } + + private static String verifyTitle(String title) { + return (title == null || title.trim().isEmpty()) ? DEFAULT_TITLE : title; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java index a8a614a..eb50f4d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java @@ -10,13 +10,8 @@ public record ProblemSetPostRequest( ) { public ProblemSet toEntity(List problemIdList) { return ProblemSet.builder() - .title(verifyTitle(this.problemSetTitle)) + .title(this.problemSetTitle) .problemIds(problemIdList) .build(); } - - // 빈 타이틀 "제목 없음"으로 임시 세팅 - private static String verifyTitle(String title) { - return (title == null || title.trim().isEmpty()) ? "제목 없음" : title; - } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java index c6fa190..704d9af 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java @@ -16,7 +16,7 @@ public static ProblemSetGetResponse of(ProblemSet problemSet, List problemThumbnailResponses; - public ProblemSetSearchGetResponse(String problemSetTitle, - List problemThumbnailResponses) { + public ProblemSetSearchGetResponse( + String problemSetTitle, List problemThumbnailResponses + ) { this.problemSetTitle = problemSetTitle; this.problemThumbnailResponses = problemThumbnailResponses; } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java index 78251b2..b563593 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -11,17 +11,15 @@ public record ProblemSummaryResponse( String practiceTestName, String comment, String mainProblemImageUrl, - List tagNames, - int sequence + List tagNames ) { - public static ProblemSummaryResponse of(Problem problem, int sequence, String practiceTestName, List tagNames) { + public static ProblemSummaryResponse of(Problem problem, String practiceTestName, List tagNames) { return ProblemSummaryResponse.builder() .problemId(problem.getId().toString()) .number(problem.getNumber()) .comment(problem.getComment()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) - .sequence(sequence) .practiceTestName(practiceTestName) .tagNames(tagNames) .build(); diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index dcd9661..52474a1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -33,7 +33,7 @@ public List search(String problemSetTitle, String p .distinct() .transform(GroupBy.groupBy(problemSet.id).list( Projections.constructor(ProblemSetSearchGetResponse.class, - problemSet.title, + problemSet.title.value, GroupBy.list( Projections.constructor(ProblemThumbnailResponse.class, problem.mainProblemImageUrl @@ -44,7 +44,7 @@ public List search(String problemSetTitle, String p } private BooleanExpression containsProblemSetTitle(String problemSetTitle) { - return (problemSetTitle == null || problemSetTitle.isEmpty()) ? null : problemSet.title.containsIgnoreCase(problemSetTitle); + return (problemSetTitle == null || problemSetTitle.isEmpty()) ? null : problemSet.title.value.containsIgnoreCase(problemSetTitle); } private BooleanExpression containsProblemTitle(String problemTitle) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java index d704b7b..20aae25 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java @@ -4,6 +4,7 @@ import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; @@ -31,14 +32,14 @@ public ProblemSetGetResponse getProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); List problemSummaries = new ArrayList<>(); - for (int i = 0; i < problemSet.getProblemIds().size(); i++) { - Problem problem = problemRepository.findByIdElseThrow(problemSet.getProblemIds().get(i)); + for (ProblemId problemId : problemSet.getProblemIds()) { + Problem problem = problemRepository.findByIdElseThrow(problemId); PracticeTestTag practiceTestTag = practiceTestTagRepository.findByIdElseThrow(problem.getPracticeTestId()); - List tagNames = conceptTagRepository.findByIdInElseThrow(problem.getConceptTagIds()) + List tagNames = conceptTagRepository.findAllByIdsElseThrow(problem.getConceptTagIds()) .stream() .map(ConceptTag::getName) .toList(); - problemSummaries.add(ProblemSummaryResponse.of(problem, i, practiceTestTag.getName(), tagNames)); + problemSummaries.add(ProblemSummaryResponse.of(problem, practiceTestTag.getName(), tagNames)); } return ProblemSetGetResponse.of(problemSet, problemSummaries); } diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index f9ce288..50c5d4e 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -58,7 +58,7 @@ void setUp() { .orElseThrow(() -> new IllegalArgumentException("문항세트를 찾을 수 없습니다.")); assertThat(savedProblemSet).isNotNull(); - assertThat(savedProblemSet.getTitle()).isEqualTo("초기 문항세트"); + assertThat(savedProblemSet.getTitle().getValue()).isEqualTo("초기 문항세트"); assertThat(savedProblemSet.getProblemIds()).hasSize(3); assertThat(savedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001001"); assertThat(savedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001002"); @@ -100,7 +100,7 @@ void setUp() { // then ProblemSet updatedProblemSet = problemSetRepository.findByIdElseThrow(problemSetId); - assertThat(updatedProblemSet.getTitle()).isEqualTo("업데이트된 문항세트"); + assertThat(updatedProblemSet.getTitle().getValue()).isEqualTo("업데이트된 문항세트"); assertThat(updatedProblemSet.getProblemIds()).hasSize(2); assertThat(updatedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001002"); assertThat(updatedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001003"); @@ -174,8 +174,8 @@ void setUp() { ProblemSet nullTitleSavedProblemSet = problemSetRepository.findByIdElseThrow(nullTitleProblemSetId); - assertThat(emptyTitleSavedProblemSet.getTitle()).isEqualTo("제목 없음"); // 빈 문자열 제목 테스트 - assertThat(nullTitleSavedProblemSet.getTitle()).isEqualTo("제목 없음"); // null 제목 테스트 + assertThat(emptyTitleSavedProblemSet.getTitle().getValue()).isEqualTo("제목 없음"); // 빈 문자열 제목 테스트 + assertThat(nullTitleSavedProblemSet.getTitle().getValue()).isEqualTo("제목 없음"); // null 제목 테스트 } @Test From b929a2305c1651892915a132c52f7a5b19ec3c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Sat, 8 Feb 2025 16:41:12 +0900 Subject: [PATCH 063/115] =?UTF-8?q?[fix/#36]=20swagger=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20url=20=EC=84=A0=ED=83=9D=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/ChildProblemMapperImpl.java | 2 +- .../service/mapper/ProblemMapperImpl.java | 2 +- .../domain/problemset/domain/QProblemSet.java | 54 +++++++++++++++++++ .../domain/publish/domain/QPublish.java | 49 +++++++++++++++++ .../config/properties/PropertiesConfig.java | 4 +- .../global/config/swagger/SwaggerConfig.java | 20 ++++++- .../properties/swagger/SwaggerProperties.java | 15 ++++++ src/main/resources/application-dev.yml | 9 ++++ src/main/resources/application-local.yml | 5 ++ src/main/resources/application-prod.yml | 9 ++++ 10 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/publish/domain/QPublish.java create mode 100644 src/main/java/com/moplus/moplus_server/global/properties/swagger/SwaggerProperties.java diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java index 53469fa..e764eac 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java @@ -10,7 +10,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-01-31T02:55:57+0900", + date = "2025-02-08T16:27:45+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index ba945c8..21c19b6 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -12,7 +12,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-01-31T02:55:56+0900", + date = "2025-02-08T16:27:45+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component diff --git a/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java new file mode 100644 index 0000000..f3b8747 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java @@ -0,0 +1,54 @@ +package com.moplus.moplus_server.domain.problemset.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QProblemSet is a Querydsl query type for ProblemSet + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QProblemSet extends EntityPathBase { + + private static final long serialVersionUID = -499971265L; + + public static final QProblemSet problemSet = new QProblemSet("problemSet"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + public final EnumPath confirmStatus = createEnum("confirmStatus", ProblemSetConfirmStatus.class); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final NumberPath id = createNumber("id", Long.class); + + public final BooleanPath isDeleted = createBoolean("isDeleted"); + + public final StringPath name = createString("name"); + + public final ListPath problemIds = this.createList("problemIds", com.moplus.moplus_server.domain.problem.domain.problem.ProblemId.class, com.moplus.moplus_server.domain.problem.domain.problem.QProblemId.class, PathInits.DIRECT2); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QProblemSet(String variable) { + super(ProblemSet.class, forVariable(variable)); + } + + public QProblemSet(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QProblemSet(PathMetadata metadata) { + super(ProblemSet.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/publish/domain/QPublish.java b/src/main/generated/com/moplus/moplus_server/domain/publish/domain/QPublish.java new file mode 100644 index 0000000..41676a4 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/publish/domain/QPublish.java @@ -0,0 +1,49 @@ +package com.moplus.moplus_server.domain.publish.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QPublish is a Querydsl query type for Publish + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QPublish extends EntityPathBase { + + private static final long serialVersionUID = 1565569153L; + + public static final QPublish publish = new QPublish("publish"); + + public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final NumberPath id = createNumber("id", Long.class); + + public final NumberPath problemSetId = createNumber("problemSetId", Long.class); + + public final DatePath publishedDate = createDate("publishedDate", java.time.LocalDate.class); + + //inherited + public final DateTimePath updatedDate = _super.updatedDate; + + public QPublish(String variable) { + super(Publish.class, forVariable(variable)); + } + + public QPublish(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QPublish(PathMetadata metadata) { + super(Publish.class, metadata); + } + +} + diff --git a/src/main/java/com/moplus/moplus_server/global/config/properties/PropertiesConfig.java b/src/main/java/com/moplus/moplus_server/global/config/properties/PropertiesConfig.java index f616ece..07dde35 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/properties/PropertiesConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/properties/PropertiesConfig.java @@ -1,11 +1,13 @@ package com.moplus.moplus_server.global.config.properties; import com.moplus.moplus_server.global.properties.jwt.JwtProperties; +import com.moplus.moplus_server.global.properties.swagger.SwaggerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; @EnableConfigurationProperties({ - JwtProperties.class + JwtProperties.class, + SwaggerProperties.class }) @Configuration public class PropertiesConfig { diff --git a/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java b/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java index f532118..63543a2 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java @@ -1,28 +1,46 @@ package com.moplus.moplus_server.global.config.swagger; +import com.moplus.moplus_server.global.properties.swagger.SwaggerProperties; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; 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 java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration +@RequiredArgsConstructor public class SwaggerConfig { + private final SwaggerProperties swaggerProperties; + private SecurityScheme createAPIKeyScheme() { return new SecurityScheme().type(SecurityScheme.Type.HTTP) .bearerFormat("JWT") .scheme("Bearer"); } + private List addServerUrl() { + return swaggerProperties.servers().stream() + .map(serverProp -> new Server() + .url(serverProp.url()) + .description(serverProp.description())) + .collect(Collectors.toList()); + } + @Bean public OpenAPI openAPI() { + return new OpenAPI().addSecurityItem(new SecurityRequirement().addList("JWT")) .components(new Components().addSecuritySchemes("JWT", createAPIKeyScheme())) .info(new Info().title("모플 API 명세서") .description("모플 API 명세서 입니다") - .version("v0.0.1")); + .version("v0.0.1")) + .servers(addServerUrl()); } } diff --git a/src/main/java/com/moplus/moplus_server/global/properties/swagger/SwaggerProperties.java b/src/main/java/com/moplus/moplus_server/global/properties/swagger/SwaggerProperties.java new file mode 100644 index 0000000..d6a3a96 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/properties/swagger/SwaggerProperties.java @@ -0,0 +1,15 @@ +package com.moplus.moplus_server.global.properties.swagger; + +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "swagger") +public record SwaggerProperties( + List servers +) { + public static record ServerProperties( + String url, + String description + ) { + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 1055bfb..0fbcc02 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -20,6 +20,15 @@ spring: connection-timeout: 3000 # 30 seconds in milliseconds keepalive-time: 600000 # 5 minutes in milliseconds +swagger: + servers: + - url: https://dev.mopl.kr + description: "mopl dev https 서버입니다." + - url: http://dev.mopl.kr + description: "mopl dev http 서버입니다." + - url: http://localhost:8080 + description: "mopl local 서버입니다." + logging: level: root: ERROR \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 72180bd..ea4aef2 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -10,3 +10,8 @@ spring: use_sql_comments: true hibernate: ddl-auto: update + +swagger: + servers: + - url: http://localhost:8080 + description: "mopl local 서버입니다." \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 1f05607..1778d4d 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -15,6 +15,15 @@ spring: connection-timeout: 3000 # 30 seconds in milliseconds keepalive-time: 600000 # 5 minutes in milliseconds +swagger: + servers: + - url: https://prod.mopl.kr + description: "mopl prod https 서버입니다." + - url: http://prod.mopl.kr + description: "mopl prod http 서버입니다." + - url: http://localhost:8080 + description: "mopl local 서버입니다." + logging: level: root: ERROR \ No newline at end of file From 40420a3947806304960ebf5ff3ebc7291dbf3d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Sat, 8 Feb 2025 16:55:30 +0900 Subject: [PATCH 064/115] =?UTF-8?q?[fix/#36]=20test=20yml=EC=97=90?= =?UTF-8?q?=EB=8F=84=20swagger=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problemset/domain/QProblemSet.java | 21 ++++++++--- .../domain/problemset/domain/QTitle.java | 37 +++++++++++++++++++ src/test/resources/application-h2test.yml | 5 +++ 3 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QTitle.java diff --git a/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java index f3b8747..3f0c463 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java @@ -18,6 +18,8 @@ public class QProblemSet extends EntityPathBase { private static final long serialVersionUID = -499971265L; + private static final PathInits INITS = PathInits.DIRECT2; + public static final QProblemSet problemSet = new QProblemSet("problemSet"); public final com.moplus.moplus_server.global.common.QBaseEntity _super = new com.moplus.moplus_server.global.common.QBaseEntity(this); @@ -31,23 +33,32 @@ public class QProblemSet extends EntityPathBase { public final BooleanPath isDeleted = createBoolean("isDeleted"); - public final StringPath name = createString("name"); - public final ListPath problemIds = this.createList("problemIds", com.moplus.moplus_server.domain.problem.domain.problem.ProblemId.class, com.moplus.moplus_server.domain.problem.domain.problem.QProblemId.class, PathInits.DIRECT2); + public final QTitle title; + //inherited public final DateTimePath updatedDate = _super.updatedDate; public QProblemSet(String variable) { - super(ProblemSet.class, forVariable(variable)); + this(ProblemSet.class, forVariable(variable), INITS); } public QProblemSet(Path path) { - super(path.getType(), path.getMetadata()); + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); } public QProblemSet(PathMetadata metadata) { - super(ProblemSet.class, metadata); + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QProblemSet(PathMetadata metadata, PathInits inits) { + this(ProblemSet.class, metadata, inits); + } + + public QProblemSet(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.title = inits.isInitialized("title") ? new QTitle(forProperty("title")) : null; } } diff --git a/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QTitle.java b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QTitle.java new file mode 100644 index 0000000..d6ad4db --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QTitle.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problemset.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QTitle is a Querydsl query type for Title + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QTitle extends BeanPath { + + private static final long serialVersionUID = -324250916L; + + public static final QTitle title = new QTitle("title"); + + public final StringPath value = createString("value"); + + public QTitle(String variable) { + super(Title.class, forVariable(variable)); + } + + public QTitle(Path<? extends Title> path) { + super(path.getType(), path.getMetadata()); + } + + public QTitle(PathMetadata metadata) { + super(Title.class, metadata); + } + +} + diff --git a/src/test/resources/application-h2test.yml b/src/test/resources/application-h2test.yml index 6095227..10c6a0f 100644 --- a/src/test/resources/application-h2test.yml +++ b/src/test/resources/application-h2test.yml @@ -36,3 +36,8 @@ cloud: cors-allowed-origins: http://localhost:8080, http://localhost:3000, + +swagger: + servers: + - url: http://localhost:8080 + description: "mopl local 서버입니다." \ No newline at end of file From f8770a0a88dc15ffd482a9bbd7f17f6627eda0c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Sat, 8 Feb 2025 21:13:40 +0900 Subject: [PATCH 065/115] =?UTF-8?q?[fix/#38]=20cors=20url=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 13 +------------ Dockerfile.prod | 13 +------------ docker-compose-dev.yml | 3 --- docker-compose-prod.yml | 3 --- src/main/resources/application.yml | 8 +++++++- 5 files changed, 9 insertions(+), 31 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9609c42..a90d3c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,17 +2,6 @@ FROM openjdk:17 ARG JAR_FILE=build/libs/*.jar COPY ${JAR_FILE} app.jar -ARG apm_agent=apm-agent/*.jar -COPY ${apm_agent} apm-agent.jar - -ARG PROFILE=dev ENV SPRING_PROFILES_ACTIVE=${PROFILE} -ENTRYPOINT ["java", \ -"-javaagent:/apm-agent.jar", \ -"-Delastic.apm.server_urls=http://apm-server:8200", \ -"-Delastic.apm.service_name=moplus-apm-agent", \ -"-Delastic.apm.application_packages=com.server", \ -"-Delastic.apm.environment=dev", \ -"-jar", \ -"/app.jar"] \ No newline at end of file +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/Dockerfile.prod b/Dockerfile.prod index c659a89..a90d3c3 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -2,17 +2,6 @@ FROM openjdk:17 ARG JAR_FILE=build/libs/*.jar COPY ${JAR_FILE} app.jar -ARG apm_agent=apm-agent/*.jar -COPY ${apm_agent} apm-agent.jar - -ARG PROFILE=dev ENV SPRING_PROFILES_ACTIVE=${PROFILE} -ENTRYPOINT ["java", \ -"-javaagent:/apm-agent.jar", \ -"-Delastic.apm.server_urls=http://apm-server:8200", \ -"-Delastic.apm.service_name=moplus-apm-agent", \ -"-Delastic.apm.application_packages=com.server", \ -"-Delastic.apm.environment=prod", \ -"-jar", \ -"/app.jar"] \ No newline at end of file +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 821939f..b3c514e 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -28,7 +28,6 @@ services: networks: - moplus-dev - nginx-network - - apm_env mysql: image: mysql:8.0 @@ -56,6 +55,4 @@ networks: moplus-dev: driver: bridge nginx-network: - external: true - apm_env: external: true \ No newline at end of file diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 10d3a57..a95bf5f 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -27,7 +27,6 @@ services: networks: - moplus-prod - nginx-network - - apm_env mysql: image: mysql:8.0 @@ -50,6 +49,4 @@ networks: moplus-prod: driver: bridge nginx-network: - external: true - apm_env: external: true \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c5ca596..3e3b140 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,4 +13,10 @@ spring: cors-allowed-origins: http://localhost:8080, - http://localhost:3000, \ No newline at end of file + https://dev.mopl.kr, + http://dev.mopl.kr, + https://prod.mopl.kr, + http://prod.mopl.kr, + http://localhost:3000, + https://www.mopl.kr, + http://www.mopl.kr \ No newline at end of file From f9868b209246e7d5c16ee1fcc9d2463215d59eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Sat, 8 Feb 2025 21:17:56 +0900 Subject: [PATCH 066/115] =?UTF-8?q?[fix/#38]=20log=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-datasource.yml | 5 ----- src/main/resources/application-dev.yml | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/resources/application-datasource.yml b/src/main/resources/application-datasource.yml index 2c7cc6b..1876f67 100644 --- a/src/main/resources/application-datasource.yml +++ b/src/main/resources/application-datasource.yml @@ -17,8 +17,3 @@ spring: dialect: org.hibernate.dialect.MySQLDialect default_batch_fetch_size: 1000 -logging: - level: - com.zaxxer.hikari: TRACE - com.zaxxer.hikari.HikariConfig: DEBUG - diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 0fbcc02..bd7d998 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -31,4 +31,4 @@ swagger: logging: level: - root: ERROR \ No newline at end of file + root: INFO \ No newline at end of file From 54e65d19588c5c92fb7e573580602ba495c44642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Sat, 8 Feb 2025 21:33:28 +0900 Subject: [PATCH 067/115] =?UTF-8?q?[fix/#38]=20Jwt=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EB=88=84=EB=9D=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-dev.yml | 5 +++++ src/main/resources/application-security.yml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index b3c514e..37eb094 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -22,6 +22,11 @@ services: - CLOUD_AWS_REGION_STATIC=${AWS_REGION} - CLOUD_AWS_REGION_AUTO=false - CLOUD_AWS_STACK_AUTO=false + - JWT_ACCESS_TOKEN_SECRET=${JWT_ACCESS_TOKEN_SECRET} + - JWT_REFRESH_TOKEN_SECRET=${JWT_REFRESH_TOKEN_SECRET} + - JWT_ACCESS_TOKEN_EXPIRATION_TIME=${JWT_ACCESS_TOKEN_EXPIRATION_TIME} # 기본값: 2시간 + - JWT_REFRESH_TOKEN_EXPIRATION_TIME=${JWT_REFRESH_TOKEN_EXPIRATION_TIME} # 기본값: 7일 + - JWT_ISSUER=${JWT_ISSUER} depends_on: - mysql diff --git a/src/main/resources/application-security.yml b/src/main/resources/application-security.yml index 266da82..c7ce091 100644 --- a/src/main/resources/application-security.yml +++ b/src/main/resources/application-security.yml @@ -1,6 +1,6 @@ jwt: access-token-secret: ${JWT_ACCESS_TOKEN_SECRET:} refresh-token-secret: ${JWT_REFRESH_TOKEN_SECRET:} - access-token-expiration-time: ${JWT_ACCESS_TOKEN_EXPIRATION_TIME:36000} #10시간 + access-token-expiration-time: ${JWT_ACCESS_TOKEN_EXPIRATION_TIME:7200} #2시간 refresh-token-expiration-time: ${JWT_REFRESH_TOKEN_EXPIRATION_TIME:604800} #7일 issuer: ${JWT_ISSUER:} \ No newline at end of file From 9834a0e1d5c07af500697bfa06ac2979898f55b7 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Sun, 9 Feb 2025 23:49:47 +0900 Subject: [PATCH 068/115] =?UTF-8?q?[feat/#42]=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1,=20=EC=97=B0=EC=9B=94=EB=B3=84=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C,=20=EC=82=AD=EC=A0=9C=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ProblemSetRepository.java | 8 ++ .../publish/controller/PublishController.java | 54 ++++++++ .../dto/request/PublishPostRequest.java | 29 ++++ .../dto/response/PublishMonthGetResponse.java | 17 +++ .../response/PublishProblemSetResponse.java | 18 +++ .../publish/repository/PublishRepository.java | 16 +++ .../publish/service/PublishDeleteService.java | 20 +++ .../publish/service/PublishGetService.java | 49 +++++++ .../publish/service/PublishSaveService.java | 24 ++++ .../global/error/exception/ErrorCode.java | 5 + .../publish/service/PublishServiceTest.java | 129 ++++++++++++++++++ src/test/resources/insert-problem-set2.sql | 11 ++ 12 files changed, 380 insertions(+) create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishProblemSetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java create mode 100644 src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java create mode 100644 src/test/resources/insert-problem-set2.sql diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java index fff031f..b145fad 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.domain.problemset.repository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,4 +12,11 @@ default ProblemSet findByIdElseThrow(Long problemSetId) { return findById(problemSetId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_SET_NOT_FOUND)); } + default void existsConfirmedActiveByIdElseThrow(Long problemSetId) { + if (!existsByIdAndIsDeletedFalseAndConfirmStatus(problemSetId, ProblemSetConfirmStatus.CONFIRMED)) { + throw new NotFoundException(ErrorCode.PROBLEM_SET_NOT_FOUND); + } + } + + boolean existsByIdAndIsDeletedFalseAndConfirmStatus(Long problemSetId, ProblemSetConfirmStatus confirmStatus); } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java new file mode 100644 index 0000000..a703911 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java @@ -0,0 +1,54 @@ +package com.moplus.moplus_server.domain.publish.controller; + +import com.moplus.moplus_server.domain.publish.dto.request.PublishPostRequest; +import com.moplus.moplus_server.domain.publish.dto.response.PublishMonthGetResponse; +import com.moplus.moplus_server.domain.publish.service.PublishDeleteService; +import com.moplus.moplus_server.domain.publish.service.PublishGetService; +import com.moplus.moplus_server.domain.publish.service.PublishSaveService; +import io.swagger.v3.oas.annotations.Operation; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/publish") +@RequiredArgsConstructor +public class PublishController { + + private final PublishGetService publishGetService; + private final PublishSaveService publishSaveService; + private final PublishDeleteService publishDeleteService; + + @GetMapping("/{year}/{month}") + @Operation(summary = "연월별 발행 조회", description = "연월별로 발행된 세트들을 조회합니다.") + public ResponseEntity<List<PublishMonthGetResponse>> getPublishMonth( + @PathVariable int year, + @PathVariable int month + ) { + return ResponseEntity.ok(publishGetService.getPublishMonth(year, month)); + } + + @PostMapping("") + @Operation(summary = "발행 생성하기", description = "특정 날짜에 문항세트를 발행합니다.") + public ResponseEntity<Long> postPublish( + @RequestBody PublishPostRequest request + ) { + return ResponseEntity.ok(publishSaveService.createPublish(request)); + } + + @DeleteMapping("/{publishId}") + @Operation(summary = "발행 삭제", description = "발행을 삭제합니다.") + public ResponseEntity<Void> deleteProblemSet( + @PathVariable Long publishId + ) { + publishDeleteService.deletePublish(publishId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java new file mode 100644 index 0000000..10c74ae --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java @@ -0,0 +1,29 @@ +package com.moplus.moplus_server.domain.publish.dto.request; + +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import java.time.LocalDate; + +public record PublishPostRequest( + LocalDate publishedDate, + Long problemSetId +) { + public PublishPostRequest { + validatePublishedDate(publishedDate); + } + + public Publish toEntity() { + return Publish.builder() + .publishedDate(this.publishedDate) + .problemSetId(this.problemSetId) + .build(); + } + + private static void validatePublishedDate(LocalDate publishedDate) { + // 발행 시점 다음날부터 발행 가능 + if (publishedDate.isBefore(LocalDate.now().plusDays(1))) { + throw new InvalidValueException(ErrorCode.INVALID_DATE_ERROR); + } + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java new file mode 100644 index 0000000..8b399a0 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java @@ -0,0 +1,17 @@ +package com.moplus.moplus_server.domain.publish.dto.response; + +import lombok.Builder; + +@Builder +public record PublishMonthGetResponse( + int day, + PublishProblemSetResponse problemSetInfo +) { + public static PublishMonthGetResponse of(int day, PublishProblemSetResponse problemSetInfos) { + + return PublishMonthGetResponse.builder() + .day(day) + .problemSetInfo(problemSetInfos) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishProblemSetResponse.java b/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishProblemSetResponse.java new file mode 100644 index 0000000..1afc053 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishProblemSetResponse.java @@ -0,0 +1,18 @@ +package com.moplus.moplus_server.domain.publish.dto.response; + +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import lombok.Builder; + +@Builder +public record PublishProblemSetResponse( + Long id, + String title +) { + public static PublishProblemSetResponse of(ProblemSet problemSet) { + + return PublishProblemSetResponse.builder() + .id(problemSet.getId()) + .title(problemSet.getTitle().getValue()) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java b/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java new file mode 100644 index 0000000..0f7433d --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java @@ -0,0 +1,16 @@ +package com.moplus.moplus_server.domain.publish.repository; + +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; +import java.time.LocalDate; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PublishRepository extends JpaRepository<Publish, Long> { + List<Publish> findByPublishedDateBetween(LocalDate startDate, LocalDate endDate); + + default Publish findByIdElseThrow(Long publishId) { + return findById(publishId).orElseThrow(() -> new NotFoundException(ErrorCode.PUBLISH_NOT_FOUND)); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java new file mode 100644 index 0000000..75d9e9c --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java @@ -0,0 +1,20 @@ +package com.moplus.moplus_server.domain.publish.service; + +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PublishDeleteService { + + private final PublishRepository publishRepository; + + @Transactional + public void deletePublish(Long publishId) { + Publish publish = publishRepository.findByIdElseThrow(publishId); + publishRepository.delete(publish); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java new file mode 100644 index 0000000..e299e6b --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java @@ -0,0 +1,49 @@ +package com.moplus.moplus_server.domain.publish.service; + +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.dto.response.PublishMonthGetResponse; +import com.moplus.moplus_server.domain.publish.dto.response.PublishProblemSetResponse; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PublishGetService { + + private final PublishRepository publishRepository; + private final ProblemSetRepository problemSetRepository; + + @Transactional(readOnly = true) + public List<PublishMonthGetResponse> getPublishMonth(int year, int month) { + if (month < 1 || month > 12) { + throw new InvalidValueException(ErrorCode.INVALID_MONTH_ERROR); + } + LocalDate startDate = LocalDate.of(year, month, 1); + LocalDate endDate = startDate.withDayOfMonth(startDate.lengthOfMonth()); + + // 주어진 월에 해당하는 모든 Publish 조회 + List<Publish> publishes = publishRepository.findByPublishedDateBetween(startDate, endDate); + + // 데이터를 day 기준으로 매핑 + return publishes.stream() + .map(publish -> { + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(publish.getProblemSetId()); + PublishProblemSetResponse problemSetResponse = PublishProblemSetResponse.of(problemSet); + + return PublishMonthGetResponse.of( + publish.getPublishedDate().getDayOfMonth(), + problemSetResponse + ); + }) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java new file mode 100644 index 0000000..9da928f --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java @@ -0,0 +1,24 @@ +package com.moplus.moplus_server.domain.publish.service; + +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.dto.request.PublishPostRequest; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PublishSaveService { + + private final ProblemSetRepository problemSetRepository; + private final PublishRepository publishRepository; + + @Transactional + public Long createPublish(PublishPostRequest request) { + problemSetRepository.existsConfirmedActiveByIdElseThrow(request.problemSetId()); + Publish publish = request.toEntity(); + return publishRepository.save(publish).getId(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 747ef27..8395524 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -57,6 +57,11 @@ public enum ErrorCode { //문항세트 PROBLEM_SET_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 문항세트를 찾을 수 없습니다"), EMPTY_PROBLEMS_ERROR(HttpStatus.BAD_REQUEST, "적어도 1개의 문항을 등록해주세요"), + + // 발행 + INVALID_MONTH_ERROR(HttpStatus.BAD_REQUEST, "유효하지 않은 월입니다."), + INVALID_DATE_ERROR(HttpStatus.BAD_REQUEST, "오늘 이후 날짜에만 발행이 가능합니다."), + PUBLISH_NOT_FOUND(HttpStatus.NOT_FOUND, "발행 정보를 찾을 수 없습니다"), ; diff --git a/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java new file mode 100644 index 0000000..66f6cbe --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java @@ -0,0 +1,129 @@ +package com.moplus.moplus_server.domain.publish.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.dto.request.PublishPostRequest; +import com.moplus.moplus_server.domain.publish.dto.response.PublishMonthGetResponse; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import com.moplus.moplus_server.global.error.exception.NotFoundException; +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@ActiveProfiles("h2test") +@Sql({"/insert-problem.sql", "/insert-problem-set2.sql"}) +@SpringBootTest +public class PublishServiceTest { + + @Autowired + private PublishSaveService publishSaveService; + + @Autowired + private PublishDeleteService publishDeleteService; + + @Autowired + private PublishGetService publishGetService; + + @Autowired + private PublishRepository publishRepository; + + private PublishPostRequest publishPostRequest; + + @BeforeEach + void setUp() { + // 발행 요청 데이터 생성 + publishPostRequest = new PublishPostRequest( + LocalDate.now().plusDays(1), // 내일부터 발행 가능 + 1L + ); + } + + @Test + void 발행_생성_테스트() { + // when + Long publishId = publishSaveService.createPublish(publishPostRequest); + + // then + Publish savedPublish = publishRepository.findByIdElseThrow(publishId); + + assertThat(savedPublish).isNotNull(); + assertThat(savedPublish.getPublishedDate()).isEqualTo(publishPostRequest.publishedDate()); + assertThat(savedPublish.getProblemSetId()).isEqualTo(1L); + } + + @Test + void 발행_삭제_테스트() { + // given + Long publishId = publishSaveService.createPublish(publishPostRequest); + + // when + publishDeleteService.deletePublish(publishId); + + // then + assertThatThrownBy(() -> publishRepository.findByIdElseThrow(publishId)) + .isInstanceOf(NotFoundException.class) + .hasMessageContaining(ErrorCode.PUBLISH_NOT_FOUND.getMessage()); + } + + @Test + void 월별_발행_조회_테스트() { + // given + publishSaveService.createPublish(new PublishPostRequest( + LocalDate.of(2025, 2, 10), + 1L + )); + + publishSaveService.createPublish(new PublishPostRequest( + LocalDate.of(2025, 2, 15), + 1L + )); + + // when + List<PublishMonthGetResponse> publishList = publishGetService.getPublishMonth(2025, 2); + + // then + assertThat(publishList).hasSize(2); + assertThat(publishList.get(0).day()).isEqualTo(10); + assertThat(publishList.get(0).problemSetInfo().title()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + assertThat(publishList.get(1).day()).isEqualTo(15); + } + + @Test + void 유효하지_않은_월_입력시_예외_테스트() { + // when & then + assertThatThrownBy(() -> publishGetService.getPublishMonth(2025, 13)) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.INVALID_MONTH_ERROR.getMessage()); + + assertThatThrownBy(() -> publishGetService.getPublishMonth(2025, 0)) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.INVALID_MONTH_ERROR.getMessage()); + } + + @Test + void 오늘날짜_또는_과거날짜로_발행_시_예외_테스트() { + // given + LocalDate today = LocalDate.now(); + LocalDate pastDate = today.minusDays(1); + + // when & then (createPublish에서 예외 발생하도록) + assertThatThrownBy(() -> publishSaveService.createPublish(new PublishPostRequest(today, 1L))) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.INVALID_DATE_ERROR.getMessage()); + + assertThatThrownBy(() -> publishSaveService.createPublish(new PublishPostRequest(pastDate, 1L))) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.INVALID_DATE_ERROR.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/resources/insert-problem-set2.sql b/src/test/resources/insert-problem-set2.sql new file mode 100644 index 0000000..ee87ce3 --- /dev/null +++ b/src/test/resources/insert-problem-set2.sql @@ -0,0 +1,11 @@ +DELETE FROM problem_set_problems; +DELETE FROM problem_set; + +-- 문제 세트 추가 +INSERT INTO problem_set (problem_set_id, title, is_deleted, confirm_status) +VALUES (1, '2025년 5월 고2 모의고사 문제 세트', false, 'CONFIRMED'); + +-- 문제 세트에 포함된 문제 추가 +INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) +VALUES (1, '240520012001', 0), + (1, '240520012002', 1); \ No newline at end of file From d990fcd265a54be82e78d5846e20e29675d86e5d Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Mon, 10 Feb 2025 00:51:44 +0900 Subject: [PATCH 069/115] =?UTF-8?q?[feat/#42]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EB=B0=9C?= =?UTF-8?q?=ED=96=89=EB=82=A0=EC=A7=9C=20=ED=95=84=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProblemSetSearchController.java | 4 +- .../dto/response/ProblemSetGetResponse.java | 5 ++- .../response/ProblemSetSearchGetResponse.java | 8 +++- .../ProblemSetSearchRepositoryCustom.java | 4 ++ .../service/ProblemSetGetService.java | 9 +++- .../publish/repository/PublishRepository.java | 3 ++ .../ProblemSetSearchRepositoryCustomTest.java | 44 +++++++++++++++++-- .../publish/service/PublishServiceTest.java | 6 +-- src/test/resources/insert-problem-set.sql | 7 ++- 9 files changed, 79 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java index 405a66f..9a318c1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetSearchRepositoryCustom; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; import io.swagger.v3.oas.annotations.Operation; import java.util.List; import lombok.RequiredArgsConstructor; @@ -18,11 +19,12 @@ public class ProblemSetSearchController { private final ProblemSetSearchRepositoryCustom problemSetSearchRepository; + private final PublishRepository publishRepository; @GetMapping("/search") @Operation( summary = "문항세트 검색", - description = "문항세트 타이틀, 문항세트 내 포함된 개념태그, 문항세트 내 포함된 문항 타이틀로 검색합니다." + description = "문항세트 타이틀, 문항세트 내 포함된 개념태그, 문항세트 내 포함된 문항 타이틀로 검색합니다. 발행상태는 발행이면 CONFIRMED, 아니면 NOT_CONFIRMED 입니다." ) public ResponseEntity<List<ProblemSetSearchGetResponse>> search( @RequestParam(value = "problemSetTitle", required = false) String problemSetTitle, diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java index 704d9af..7c32ba7 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java @@ -2,6 +2,7 @@ import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; +import java.time.LocalDate; import java.util.List; import lombok.Builder; @@ -10,14 +11,16 @@ public record ProblemSetGetResponse( Long id, String title, ProblemSetConfirmStatus confirmStatus, + LocalDate publishedDate, List<ProblemSummaryResponse> problemSummaries ) { - public static ProblemSetGetResponse of(ProblemSet problemSet, List<ProblemSummaryResponse> problemSummaries) { + public static ProblemSetGetResponse of(ProblemSet problemSet, LocalDate publishedDate, List<ProblemSummaryResponse> problemSummaries) { return ProblemSetGetResponse.builder() .id(problemSet.getId()) .title(problemSet.getTitle().getValue()) .confirmStatus(problemSet.getConfirmStatus()) + .publishedDate(publishedDate) .problemSummaries(problemSummaries) .build(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java index 140e1c3..789b310 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java @@ -1,5 +1,7 @@ package com.moplus.moplus_server.domain.problemset.dto.response; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; +import java.time.LocalDate; import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @@ -8,12 +10,16 @@ @NoArgsConstructor public class ProblemSetSearchGetResponse { private String problemSetTitle; + private ProblemSetConfirmStatus confirmStatus; + private LocalDate publishedDate; private List<ProblemThumbnailResponse> problemThumbnailResponses; public ProblemSetSearchGetResponse( - String problemSetTitle, List<ProblemThumbnailResponse> problemThumbnailResponses + String problemSetTitle, ProblemSetConfirmStatus confirmStatus, LocalDate publishedDate, List<ProblemThumbnailResponse> problemThumbnailResponses ) { this.problemSetTitle = problemSetTitle; + this.confirmStatus = confirmStatus; + this.publishedDate = publishedDate; this.problemThumbnailResponses = problemThumbnailResponses; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index 52474a1..665da6a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -3,6 +3,7 @@ import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag; import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; import static com.moplus.moplus_server.domain.problemset.domain.QProblemSet.problemSet; +import static com.moplus.moplus_server.domain.publish.domain.QPublish.publish; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; @@ -25,6 +26,7 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p .from(problemSet) .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 + .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( containsProblemSetTitle(problemSetTitle), containsProblemTitle(problemTitle), @@ -34,6 +36,8 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p .transform(GroupBy.groupBy(problemSet.id).list( Projections.constructor(ProblemSetSearchGetResponse.class, problemSet.title.value, + problemSet.confirmStatus, + publish.publishedDate, // 발행되지 않은 경우 null 반환 GroupBy.list( Projections.constructor(ProblemThumbnailResponse.class, problem.mainProblemImageUrl diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java index 20aae25..3392a8d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java @@ -11,6 +11,9 @@ import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSummaryResponse; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; @@ -25,11 +28,15 @@ public class ProblemSetGetService { private final ProblemRepository problemRepository; private final PracticeTestTagRepository practiceTestTagRepository; private final ConceptTagRepository conceptTagRepository; + private final PublishRepository publishRepository; @Transactional(readOnly = true) public ProblemSetGetResponse getProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + LocalDate publishedDate = publishRepository.findByProblemSetId(problemSetId) + .map(Publish::getPublishedDate) + .orElse(null); List<ProblemSummaryResponse> problemSummaries = new ArrayList<>(); for (ProblemId problemId : problemSet.getProblemIds()) { @@ -41,6 +48,6 @@ public ProblemSetGetResponse getProblemSet(Long problemSetId) { .toList(); problemSummaries.add(ProblemSummaryResponse.of(problem, practiceTestTag.getName(), tagNames)); } - return ProblemSetGetResponse.of(problemSet, problemSummaries); + return ProblemSetGetResponse.of(problemSet, publishedDate, problemSummaries); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java b/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java index 0f7433d..0fb157d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java @@ -5,6 +5,7 @@ import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.time.LocalDate; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface PublishRepository extends JpaRepository<Publish, Long> { @@ -13,4 +14,6 @@ public interface PublishRepository extends JpaRepository<Publish, Long> { default Publish findByIdElseThrow(Long publishId) { return findById(publishId).orElseThrow(() -> new NotFoundException(ErrorCode.PUBLISH_NOT_FOUND)); } + + Optional<Publish> findByProblemSetId(Long problemSetId); } diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index 9e05157..042dfa9 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -2,8 +2,12 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; +import com.moplus.moplus_server.domain.publish.dto.request.PublishPostRequest; +import com.moplus.moplus_server.domain.publish.service.PublishSaveService; +import java.time.LocalDate; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -21,6 +25,9 @@ public class ProblemSetSearchRepositoryCustomTest { @Autowired private ProblemSetSearchRepositoryCustom problemSetSearchRepository; + @Autowired + private PublishSaveService publishSaveService; + @Test void 문항세트_타이틀_일부_포함_검색() { // when @@ -37,8 +44,9 @@ public class ProblemSetSearchRepositoryCustomTest { List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, "설명 1", null); // then - assertThat(result).hasSize(1); + assertThat(result).hasSize(2); assertThat(result.get(0).getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + assertThat(result.get(1).getProblemSetTitle()).isEqualTo("2025년 5월 고3 모의고사 문제 세트"); } @Test @@ -47,8 +55,9 @@ public class ProblemSetSearchRepositoryCustomTest { List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, null, List.of("미분 개념")); // then - assertThat(result).hasSize(1); + assertThat(result).hasSize(2); assertThat(result.get(0).getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + assertThat(result.get(1).getProblemSetTitle()).isEqualTo("2025년 5월 고3 모의고사 문제 세트"); } @Test @@ -67,7 +76,7 @@ public class ProblemSetSearchRepositoryCustomTest { List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, null, null); // then - assertThat(result).hasSize(1); + assertThat(result).hasSize(2); } @Test @@ -88,4 +97,33 @@ public class ProblemSetSearchRepositoryCustomTest { assertThat(problems.get(0).getMainProblemImageUrl()).isEqualTo("mainProblem.png1"); assertThat(problems.get(1).getMainProblemImageUrl()).isEqualTo("mainProblem.png2"); } + + @Test + void 발행되지_않은_문항세트는_NOT_CONFIRMED_테스트() { + // when + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고2 모의고사", null, null); + + // then + assertThat(result).hasSize(1); + ProblemSetSearchGetResponse response = result.get(0); + + assertThat(response.getConfirmStatus()).isEqualTo(ProblemSetConfirmStatus.NOT_CONFIRMED); + assertThat(response.getPublishedDate()).isNull(); + } + + @Test + void 발행된_문항세트_발행날짜_테스트() { + // given + LocalDate publishDate = LocalDate.now().plusDays(1); + publishSaveService.createPublish(new PublishPostRequest(publishDate, 2L)); + + // when + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고3 모의고사", null, null); + + // then + assertThat(result).hasSize(1); + ProblemSetSearchGetResponse response = result.get(0); + + assertThat(response.getPublishedDate()).isEqualTo(publishDate); + } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java index 66f6cbe..463bf7d 100644 --- a/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java @@ -80,17 +80,17 @@ void setUp() { void 월별_발행_조회_테스트() { // given publishSaveService.createPublish(new PublishPostRequest( - LocalDate.of(2025, 2, 10), + LocalDate.of(2025, 3, 10), 1L )); publishSaveService.createPublish(new PublishPostRequest( - LocalDate.of(2025, 2, 15), + LocalDate.of(2025, 3, 15), 1L )); // when - List<PublishMonthGetResponse> publishList = publishGetService.getPublishMonth(2025, 2); + List<PublishMonthGetResponse> publishList = publishGetService.getPublishMonth(2025, 3); // then assertThat(publishList).hasSize(2); diff --git a/src/test/resources/insert-problem-set.sql b/src/test/resources/insert-problem-set.sql index e9c8af1..051949c 100644 --- a/src/test/resources/insert-problem-set.sql +++ b/src/test/resources/insert-problem-set.sql @@ -4,8 +4,13 @@ DELETE FROM problem_set; -- 문제 세트 추가 INSERT INTO problem_set (problem_set_id, title, is_deleted, confirm_status) VALUES (1, '2025년 5월 고2 모의고사 문제 세트', false, 'NOT_CONFIRMED'); +INSERT INTO problem_set (problem_set_id, title, is_deleted, confirm_status) +VALUES (2, '2025년 5월 고3 모의고사 문제 세트', false, 'CONFIRMED'); -- 문제 세트에 포함된 문제 추가 INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) VALUES (1, '240520012001', 0), - (1, '240520012002', 1); \ No newline at end of file + (1, '240520012002', 1); +INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) +VALUES (2, '240520012001', 0), + (2, '240520012002', 1); \ No newline at end of file From 5e1e097183f816e3711fdb453c966347466f1e0f Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Mon, 10 Feb 2025 01:13:14 +0900 Subject: [PATCH 070/115] =?UTF-8?q?[feat/#42]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=BB=A8=ED=8E=8C=20=EC=8B=9C=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=AC=B8?= =?UTF-8?q?=ED=95=ADid=20=EC=97=90=EB=9F=AC=EB=A9=94=EC=8B=9C=EC=A7=80?= =?UTF-8?q?=EC=97=90=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problemset/domain/ProblemSet.java | 13 ++++++++----- .../global/error/exception/ErrorCode.java | 2 +- .../domain/problemset/ProblemSetServiceTest.java | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index cfa041e..bcd4d16 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -65,11 +65,14 @@ public void deleteProblemSet() { public void toggleConfirm(List<Problem> problems) { if(this.confirmStatus == ProblemSetConfirmStatus.NOT_CONFIRMED){ - // 문항 유효성 검사 - for (Problem problem : problems) { - if (!problem.isValid()) { - throw new InvalidValueException(ErrorCode.INVALID_CONFIRM_PROBLEM); - } + List<String> invalidProblemIds = problems.stream() + .filter(problem -> !problem.isValid()) + .map(problem -> problem.getId().getId()) + .toList(); + if (!invalidProblemIds.isEmpty()) { + String message = ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage() + + String.join("번 ", invalidProblemIds) + "번"; + throw new InvalidValueException(message, ErrorCode.INVALID_CONFIRM_PROBLEM); } } this.confirmStatus = this.confirmStatus.toggle(); diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 8395524..6b645b8 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -32,7 +32,7 @@ public enum ErrorCode { PROBLEM_ALREADY_EXIST(HttpStatus.CONFLICT, "해당 문제는 이미 존재합니다"), INVALID_MULTIPLE_CHOICE_ANSWER(HttpStatus.BAD_REQUEST, "객관식 문제의 정답은 1~5 사이의 숫자여야 합니다"), INVALID_SHORT_NUMBER_ANSWER(HttpStatus.BAD_REQUEST, "주관식 문제의 정답은 0~999 사이의 숫자여야 합니다"), - INVALID_CONFIRM_PROBLEM(HttpStatus.BAD_REQUEST, "문항의 모든 요소를 등록해야 컨펌을 완료할 수 있습니다."), + INVALID_CONFIRM_PROBLEM(HttpStatus.BAD_REQUEST, "유효하지 않은 문항들 : "), //새끼 문항 CHILD_PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 새끼 문제를 찾을 수 없습니다"), diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index 50c5d4e..d682201 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -135,6 +135,7 @@ void setUp() { // when & then assertThatThrownBy(() -> problemSetUpdateService.toggleConfirmProblemSet(problemSetId)) .isInstanceOf(InvalidValueException.class) + .hasMessageContaining("24052001004번") // 메시지에 포함된 ID 확인 .hasMessageContaining(ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage()); } From b2ce4d158fc478369e631de00c4c56b10d763a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 11 Feb 2025 01:37:04 +0900 Subject: [PATCH 071/115] =?UTF-8?q?[fix/#44]=20jwt=20=EC=A0=84=EB=8B=AC=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20cors=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/EmailPasswordSuccessHandler.java | 19 +++++++++++++++---- src/main/resources/application.yml | 2 ++ .../auth/controller/AuthControllerTest.java | 10 +++++----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java index 9fe78fc..f5ebad6 100644 --- a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java +++ b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java @@ -1,10 +1,13 @@ package com.moplus.moplus_server.global.security.handler; +import com.fasterxml.jackson.databind.ObjectMapper; import com.moplus.moplus_server.domain.member.domain.Member; -import com.moplus.moplus_server.global.security.AuthConstants; import com.moplus.moplus_server.global.security.utils.JwtUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; @@ -15,15 +18,23 @@ public class EmailPasswordSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private final JwtUtil jwtUtil; + private final ObjectMapper objectMapper = new ObjectMapper(); // JSON 변환을 위한 ObjectMapper @Override public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, - final Authentication authentication) { + final Authentication authentication) throws IOException { Member member = (Member) authentication.getPrincipal(); String accessToken = jwtUtil.generateAccessToken(member); String refreshToken = jwtUtil.generateRefreshToken(member); - response.addHeader(AuthConstants.AUTH_HEADER, AuthConstants.TOKEN_TYPE + " " + accessToken); - response.addHeader(AuthConstants.REFRESH_TOKEN_HEADER, AuthConstants.TOKEN_TYPE + " " + refreshToken); + + // JSON 응답 생성 + Map<String, String> tokenResponse = new HashMap<>(); + tokenResponse.put("accessToken", accessToken); + tokenResponse.put("refreshToken", refreshToken); + + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(objectMapper.writeValueAsString(tokenResponse)); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3e3b140..6e09489 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -17,6 +17,8 @@ cors-allowed-origins: http://dev.mopl.kr, https://prod.mopl.kr, http://prod.mopl.kr, + http://localhost:3000, + http://localhost:5173, https://www.mopl.kr, http://www.mopl.kr \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java b/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java index 95ee4e7..4d87cb3 100644 --- a/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.auth.controller; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; @@ -56,12 +56,12 @@ class 어드민_로그인 { mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/auth/admin/login") .contentType("application/json") .content(requestBody)) - .andExpect(status().isOk()) // 200 응답 확인 - .andExpect(header().exists("Authorization")) - .andExpect(header().exists("RefreshToken")); - + .andExpect(status().isOk()) // HTTP 200 응답 확인 + .andExpect(jsonPath("$.accessToken").isNotEmpty()) // accessToken 필드 존재 여부 확인 + .andExpect(jsonPath("$.refreshToken").isNotEmpty()); // refreshToken 필드 존재 여부 확인 } + @Test void 잘못된_요청_본문() throws Exception { From 2ba6044e08c3bfc01e4d560ef00a2d0eb6a4a0c0 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Tue, 11 Feb 2025 14:40:39 +0900 Subject: [PATCH 072/115] =?UTF-8?q?[fix/#42]=20=EB=B0=9C=ED=96=89=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/publish/domain/Publish.java | 8 ++++++++ .../publish/dto/request/PublishPostRequest.java | 13 ------------- .../domain/publish/service/PublishSaveService.java | 2 ++ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java b/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java index 55a66ab..52aca88 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java @@ -1,6 +1,8 @@ package com.moplus.moplus_server.domain.publish.domain; import com.moplus.moplus_server.global.common.BaseEntity; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -34,4 +36,10 @@ public Publish(LocalDate publishedDate, Long problemSetId) { this.problemSetId = problemSetId; } + public void validatePublishedDate() { + // 발행 시점 다음날부터 발행 가능 + if (this.publishedDate.isBefore(LocalDate.now().plusDays(1))) { + throw new InvalidValueException(ErrorCode.INVALID_DATE_ERROR); + } + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java index 10c74ae..a020f43 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java @@ -1,29 +1,16 @@ package com.moplus.moplus_server.domain.publish.dto.request; import com.moplus.moplus_server.domain.publish.domain.Publish; -import com.moplus.moplus_server.global.error.exception.ErrorCode; -import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.time.LocalDate; public record PublishPostRequest( LocalDate publishedDate, Long problemSetId ) { - public PublishPostRequest { - validatePublishedDate(publishedDate); - } - public Publish toEntity() { return Publish.builder() .publishedDate(this.publishedDate) .problemSetId(this.problemSetId) .build(); } - - private static void validatePublishedDate(LocalDate publishedDate) { - // 발행 시점 다음날부터 발행 가능 - if (publishedDate.isBefore(LocalDate.now().plusDays(1))) { - throw new InvalidValueException(ErrorCode.INVALID_DATE_ERROR); - } - } } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java index 9da928f..d134080 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java @@ -19,6 +19,8 @@ public class PublishSaveService { public Long createPublish(PublishPostRequest request) { problemSetRepository.existsConfirmedActiveByIdElseThrow(request.problemSetId()); Publish publish = request.toEntity(); + // 발행날짜 유효성 검사 + publish.validatePublishedDate(); return publishRepository.save(publish).getId(); } } From 909bf4f845aacf62b4f40fba13d063b9901fefdc Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Tue, 11 Feb 2025 14:48:30 +0900 Subject: [PATCH 073/115] =?UTF-8?q?[fix/42]=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../publish/service/PublishGetService.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java index e299e6b..6cc6c52 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java @@ -10,6 +10,7 @@ import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.time.LocalDate; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -33,17 +34,32 @@ public List<PublishMonthGetResponse> getPublishMonth(int year, int month) { // 주어진 월에 해당하는 모든 Publish 조회 List<Publish> publishes = publishRepository.findByPublishedDateBetween(startDate, endDate); - // 데이터를 day 기준으로 매핑 + // 한 번의 쿼리로 모든 ProblemSet 조회 + Map<Long, ProblemSet> problemSetMap = getProblemSetMap(publishes); + return publishes.stream() - .map(publish -> { - ProblemSet problemSet = problemSetRepository.findByIdElseThrow(publish.getProblemSetId()); - PublishProblemSetResponse problemSetResponse = PublishProblemSetResponse.of(problemSet); - - return PublishMonthGetResponse.of( - publish.getPublishedDate().getDayOfMonth(), - problemSetResponse - ); - }) + .map(publish -> convertToResponse(publish, problemSetMap)) + .collect(Collectors.toList()); + } + + private Map<Long, ProblemSet> getProblemSetMap(List<Publish> publishes) { + List<Long> problemSetIds = publishes.stream() + .map(Publish::getProblemSetId) + .distinct() .collect(Collectors.toList()); + + return problemSetRepository.findAllById(problemSetIds).stream() + .collect(Collectors.toMap(ProblemSet::getId, problemSet -> problemSet)); + } + + private PublishMonthGetResponse convertToResponse(Publish publish, Map<Long, ProblemSet> problemSetMap) { + ProblemSet problemSet = problemSetMap.get(publish.getProblemSetId()); + if (problemSet == null) { + throw new InvalidValueException(ErrorCode.PROBLEM_SET_NOT_FOUND); + } + return PublishMonthGetResponse.of( + publish.getPublishedDate().getDayOfMonth(), + PublishProblemSetResponse.of(problemSet) + ); } } From 757b6a0d3733431a0fe3b211924e9e790182c41b Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Tue, 11 Feb 2025 15:09:52 +0900 Subject: [PATCH 074/115] =?UTF-8?q?[feat/#42]=20=EB=B0=9C=ED=96=89?= =?UTF-8?q?=EC=9A=A9=20=EC=BB=A8=ED=8E=8C=EB=90=9C=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EA=B2=80=EC=83=89=20api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProblemSetSearchController.java | 16 ++++- .../ProblemSetSearchRepositoryCustom.java | 28 +++++++++ .../ProblemSetSearchRepositoryCustomTest.java | 61 +++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java index 9a318c1..09c0b11 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java @@ -3,7 +3,6 @@ import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetSearchRepositoryCustom; -import com.moplus.moplus_server.domain.publish.repository.PublishRepository; import io.swagger.v3.oas.annotations.Operation; import java.util.List; import lombok.RequiredArgsConstructor; @@ -19,7 +18,6 @@ public class ProblemSetSearchController { private final ProblemSetSearchRepositoryCustom problemSetSearchRepository; - private final PublishRepository publishRepository; @GetMapping("/search") @Operation( @@ -34,4 +32,18 @@ public ResponseEntity<List<ProblemSetSearchGetResponse>> search( List<ProblemSetSearchGetResponse> problemSets = problemSetSearchRepository.search(problemSetTitle, problemTitle, conceptTagNames); return ResponseEntity.ok(problemSets); } + + @GetMapping("/confirm/search") + @Operation( + summary = "발행용 문항세트 검색", + description = "문항세트 타이틀, 문항세트 내 포함된 개념태그, 문항세트 내 포함된 문항 타이틀로 검색합니다. 발행상태가 CONFIRMED 문항세트만 조회됩니다.." + ) + public ResponseEntity<List<ProblemSetSearchGetResponse>> confirmSearch( + @RequestParam(value = "problemSetTitle", required = false) String problemSetTitle, + @RequestParam(value = "problemTitle", required = false) String problemTitle, + @RequestParam(value = "conceptTagNames", required = false) List<String> conceptTagNames + ) { + List<ProblemSetSearchGetResponse> problemSets = problemSetSearchRepository.confirmSearch(problemSetTitle, problemTitle, conceptTagNames); + return ResponseEntity.ok(problemSets); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index 665da6a..9bf6d22 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -2,6 +2,7 @@ import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag; import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; +import static com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus.CONFIRMED; import static com.moplus.moplus_server.domain.problemset.domain.QProblemSet.problemSet; import static com.moplus.moplus_server.domain.publish.domain.QPublish.publish; @@ -47,6 +48,33 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p )); } + public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, String problemTitle, List<String> conceptTagNames) { + return queryFactory + .from(problemSet) + .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 + .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 + .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 + .where( + problemSet.confirmStatus.eq(CONFIRMED), + containsProblemSetTitle(problemSetTitle), + containsProblemTitle(problemTitle), + containsConceptTagNames(conceptTagNames) + ) + .distinct() + .transform(GroupBy.groupBy(problemSet.id).list( + Projections.constructor(ProblemSetSearchGetResponse.class, + problemSet.title.value, + problemSet.confirmStatus, + publish.publishedDate, // 발행되지 않은 경우 null 반환 + GroupBy.list( + Projections.constructor(ProblemThumbnailResponse.class, + problem.mainProblemImageUrl + ) + ) + ) + )); + } + private BooleanExpression containsProblemSetTitle(String problemSetTitle) { return (problemSetTitle == null || problemSetTitle.isEmpty()) ? null : problemSet.title.value.containsIgnoreCase(problemSetTitle); } diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index 042dfa9..5085caf 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -5,6 +5,7 @@ import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; +import com.moplus.moplus_server.domain.problemset.service.ProblemSetUpdateService; import com.moplus.moplus_server.domain.publish.dto.request.PublishPostRequest; import com.moplus.moplus_server.domain.publish.service.PublishSaveService; import java.time.LocalDate; @@ -28,6 +29,10 @@ public class ProblemSetSearchRepositoryCustomTest { @Autowired private PublishSaveService publishSaveService; + @Autowired + private ProblemSetUpdateService problemSetUpdateService; + + @Test void 문항세트_타이틀_일부_포함_검색() { // when @@ -126,4 +131,60 @@ public class ProblemSetSearchRepositoryCustomTest { assertThat(response.getPublishedDate()).isEqualTo(publishDate); } + + @Test + void 컴펌된_문항세트_검색_테스트() { + // given: CONFIRMED 상태의 문제 세트만 발행 + LocalDate publishDate = LocalDate.now(); + publishSaveService.createPublish(new PublishPostRequest(publishDate.plusDays(5), 2L)); // CONFIRMED 상태 + + // when: publishSearch 실행 (CONFIRMED 상태만 검색되어야 함) + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.confirmSearch( + "고", + "설명", + List.of("미분 개념") + ); + + // then + assertThat(result).isNotEmpty(); + assertThat(result).allSatisfy(response -> + assertThat(response.getConfirmStatus()).isEqualTo(ProblemSetConfirmStatus.CONFIRMED) + ); + } + + @Test + void 컴펌되지_않은_문항세트_검색_결과없음_테스트() { + // given: 발행되지 않은 문제 세트 존재 + problemSetUpdateService.toggleConfirmProblemSet(2L); + + // when: 발행된 문제 세트만 조회하는 publishSearch 실행 + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.confirmSearch( + null, + null, + null + ); + + // then: CONFIRMED 상태가 없는 경우, 결과가 비어 있어야 함 + assertThat(result).isEmpty(); + } + + @Test + void 컨펌된_문항세트_정확한_발행날짜_검증() { + // given + LocalDate publishDate = LocalDate.now().plusDays(1); + publishSaveService.createPublish(new PublishPostRequest(publishDate, 2L)); // 발행 처리 + + // when + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.confirmSearch( + "고3 모의고사", + null, + null + ); + + // then + assertThat(result).hasSize(1); + ProblemSetSearchGetResponse response = result.get(0); + + assertThat(response.getPublishedDate()).isEqualTo(publishDate); + } } \ No newline at end of file From 79b5b53f0f34aeb0c81f26ed1be880c22adfffc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 12 Feb 2025 03:20:52 +0900 Subject: [PATCH 075/115] =?UTF-8?q?[feat/#34]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=B2=AB=20=EC=83=9D=EC=84=B1=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/childProblem/QChildProblem.java | 4 +- .../problem/domain/problem/QProblem.java | 18 +-- .../domain/problem/QProblemAdminId.java | 37 ++++++ .../problem/domain/problem/QProblemId.java | 37 ------ .../mapper/ChildProblemMapperImpl.java | 6 +- .../service/mapper/ProblemMapperImpl.java | 41 +++---- .../domain/problemset/domain/QProblemSet.java | 2 +- .../auth/controller/AuthController.java | 23 +--- .../auth/dto/response/TokenResponse.java | 7 ++ .../problem/controller/ProblemController.java | 2 +- .../domain/problem/domain/Answer.java | 12 +- .../domain/childProblem/ChildProblem.java | 18 +-- .../problem/domain/practiceTest/Subject.java | 2 +- .../problem/domain/problem/AnswerType.java | 37 ++++++ .../problem/domain/problem/Problem.java | 73 ++++++----- .../{ProblemId.java => ProblemAdminId.java} | 6 +- .../domain/problem/ProblemIdService.java | 25 ++-- .../problem/domain/problem/ProblemType.java | 33 +---- .../dto/request/ChildProblemPostRequest.java | 4 +- .../request/ChildProblemUpdateRequest.java | 4 +- .../dto/request/ProblemPostRequest.java | 30 ++--- .../dto/request/ProblemUpdateRequest.java | 2 +- .../dto/response/ChildProblemGetResponse.java | 6 +- .../dto/response/ProblemGetResponse.java | 8 +- .../problem/repository/ProblemRepository.java | 20 +--- .../problem/service/ProblemDeleteService.java | 6 +- .../problem/service/ProblemGetService.java | 4 +- .../problem/service/ProblemSaveService.java | 24 +--- .../problem/service/ProblemUpdateService.java | 6 +- .../problem/service/mapper/ProblemMapper.java | 10 +- .../domain/problemset/domain/ProblemSet.java | 22 ++-- .../dto/request/ProblemSetPostRequest.java | 6 +- .../dto/response/ProblemSummaryResponse.java | 4 +- .../ProblemSetSearchRepositoryCustom.java | 15 ++- .../service/ProblemSetGetService.java | 6 +- .../service/ProblemSetSaveService.java | 10 +- .../service/ProblemSetUpdateService.java | 20 ++-- .../resources/templates/answerInputForm.html | 113 ++++++++++-------- .../resources/templates/imageUploadPage.html | 12 +- .../resources/templates/practiceTestList.html | 8 +- .../resources/templates/testInputForm.html | 56 +++++---- ...st.java => ProblemAdminIdServiceTest.java} | 14 +-- .../service/ProblemSaveServiceTest.java | 28 ++--- .../service/ProblemUpdateServiceTest.java | 16 +-- .../problemset/ProblemSetServiceTest.java | 26 ++-- .../service/ProblemSetGetServiceTest.java | 10 +- 46 files changed, 445 insertions(+), 428 deletions(-) create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java delete mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/auth/dto/response/TokenResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java rename src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/{ProblemId.java => ProblemAdminId.java} (70%) rename src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/{ProblemIdServiceTest.java => ProblemAdminIdServiceTest.java} (84%) diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java index 8dad7ae..b22ac5b 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java @@ -26,6 +26,8 @@ public class QChildProblem extends EntityPathBase<ChildProblem> { public final com.moplus.moplus_server.domain.problem.domain.QAnswer answer; + public final EnumPath<com.moplus.moplus_server.domain.problem.domain.problem.AnswerType> answerType = createEnum("answerType", com.moplus.moplus_server.domain.problem.domain.problem.AnswerType.class); + public final SetPath<Long, NumberPath<Long>> conceptTagIds = this.<Long, NumberPath<Long>>createSet("conceptTagIds", Long.class, NumberPath.class, PathInits.DIRECT2); //inherited @@ -35,8 +37,6 @@ public class QChildProblem extends EntityPathBase<ChildProblem> { public final StringPath imageUrl = createString("imageUrl"); - public final EnumPath<com.moplus.moplus_server.domain.problem.domain.problem.ProblemType> problemType = createEnum("problemType", com.moplus.moplus_server.domain.problem.domain.problem.ProblemType.class); - public final NumberPath<Integer> sequence = createNumber("sequence", Integer.class); //inherited diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java index fb35ae5..41612a2 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java @@ -26,37 +26,41 @@ public class QProblem extends EntityPathBase<Problem> { public final com.moplus.moplus_server.domain.problem.domain.QAnswer answer; - public final ListPath<com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem, com.moplus.moplus_server.domain.problem.domain.childProblem.QChildProblem> childProblems = this.<com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem, com.moplus.moplus_server.domain.problem.domain.childProblem.QChildProblem>createList("childProblems", com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem.class, com.moplus.moplus_server.domain.problem.domain.childProblem.QChildProblem.class, PathInits.DIRECT2); + public final EnumPath<AnswerType> answerType = createEnum("answerType", AnswerType.class); - public final StringPath comment = createString("comment"); + public final ListPath<com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem, com.moplus.moplus_server.domain.problem.domain.childProblem.QChildProblem> childProblems = this.<com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem, com.moplus.moplus_server.domain.problem.domain.childProblem.QChildProblem>createList("childProblems", com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem.class, com.moplus.moplus_server.domain.problem.domain.childProblem.QChildProblem.class, PathInits.DIRECT2); public final SetPath<Long, NumberPath<Long>> conceptTagIds = this.<Long, NumberPath<Long>>createSet("conceptTagIds", Long.class, NumberPath.class, PathInits.DIRECT2); //inherited public final DateTimePath<java.time.LocalDateTime> createdDate = _super.createdDate; - public final QProblemId id; - - public final BooleanPath isPublished = createBoolean("isPublished"); + public final NumberPath<Long> id = createNumber("id", Long.class); - public final BooleanPath isVariation = createBoolean("isVariation"); + public final BooleanPath isConfirmed = createBoolean("isConfirmed"); public final StringPath mainAnalysisImageUrl = createString("mainAnalysisImageUrl"); public final StringPath mainProblemImageUrl = createString("mainProblemImageUrl"); + public final StringPath memo = createString("memo"); + public final NumberPath<Integer> number = createNumber("number", Integer.class); public final NumberPath<Long> practiceTestId = createNumber("practiceTestId", Long.class); public final StringPath prescriptionImageUrl = createString("prescriptionImageUrl"); + public final QProblemAdminId problemAdminId; + public final EnumPath<ProblemType> problemType = createEnum("problemType", ProblemType.class); public final StringPath readingTipImageUrl = createString("readingTipImageUrl"); public final StringPath seniorTipImageUrl = createString("seniorTipImageUrl"); + public final StringPath title = createString("title"); + //inherited public final DateTimePath<java.time.LocalDateTime> updatedDate = _super.updatedDate; @@ -79,7 +83,7 @@ public QProblem(PathMetadata metadata, PathInits inits) { public QProblem(Class<? extends Problem> type, PathMetadata metadata, PathInits inits) { super(type, metadata, inits); this.answer = inits.isInitialized("answer") ? new com.moplus.moplus_server.domain.problem.domain.QAnswer(forProperty("answer")) : null; - this.id = inits.isInitialized("id") ? new QProblemId(forProperty("id")) : null; + this.problemAdminId = inits.isInitialized("problemAdminId") ? new QProblemAdminId(forProperty("problemAdminId")) : null; } } diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java new file mode 100644 index 0000000..d7e56ee --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QProblemAdminId is a Querydsl query type for ProblemAdminId + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QProblemAdminId extends BeanPath<ProblemAdminId> { + + private static final long serialVersionUID = 348147768L; + + public static final QProblemAdminId problemAdminId = new QProblemAdminId("problemAdminId"); + + public final StringPath id = createString("id"); + + public QProblemAdminId(String variable) { + super(ProblemAdminId.class, forVariable(variable)); + } + + public QProblemAdminId(Path<? extends ProblemAdminId> path) { + super(path.getType(), path.getMetadata()); + } + + public QProblemAdminId(PathMetadata metadata) { + super(ProblemAdminId.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java deleted file mode 100644 index ca7809a..0000000 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.moplus.moplus_server.domain.problem.domain.problem; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QProblemId is a Querydsl query type for ProblemId - */ -@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") -public class QProblemId extends BeanPath<ProblemId> { - - private static final long serialVersionUID = -1309260563L; - - public static final QProblemId problemId = new QProblemId("problemId"); - - public final StringPath id = createString("id"); - - public QProblemId(String variable) { - super(ProblemId.class, forVariable(variable)); - } - - public QProblemId(Path<? extends ProblemId> path) { - super(path.getType(), path.getMetadata()); - } - - public QProblemId(PathMetadata metadata) { - super(ProblemId.class, metadata); - } - -} - diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java index e764eac..dc056d0 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java @@ -10,7 +10,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-08T16:27:45+0900", + date = "2025-02-12T02:55:03+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component @@ -25,7 +25,7 @@ public ChildProblem from(ChildProblemPostRequest request) { ChildProblem.ChildProblemBuilder childProblem = ChildProblem.builder(); childProblem.imageUrl( request.imageUrl() ); - childProblem.problemType( request.problemType() ); + childProblem.answerType( request.answerType() ); childProblem.answer( request.answer() ); Set<Long> set = request.conceptTagIds(); if ( set != null ) { @@ -45,7 +45,7 @@ public ChildProblem from(ChildProblemUpdateRequest request) { ChildProblem.ChildProblemBuilder childProblem = ChildProblem.builder(); childProblem.imageUrl( request.imageUrl() ); - childProblem.problemType( request.problemType() ); + childProblem.answerType( request.answerType() ); childProblem.answer( request.answer() ); Set<Long> set = request.conceptTagIds(); if ( set != null ) { diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index 21c19b6..e8b0d28 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -2,7 +2,7 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import java.util.LinkedHashSet; @@ -12,62 +12,51 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-08T16:27:45+0900", + date = "2025-02-12T02:55:03+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component public class ProblemMapperImpl implements ProblemMapper { @Override - public Problem from(ProblemPostRequest request, ProblemId problemId, PracticeTestTag practiceTestTag) { - if ( request == null && problemId == null && practiceTestTag == null ) { + public Problem from(ProblemPostRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag) { + if ( request == null && problemAdminId == null && practiceTestTag == null ) { return null; } Problem.ProblemBuilder problem = Problem.builder(); if ( request != null ) { + problem.problemType( request.problemType() ); problem.number( request.number() ); - problem.answer( request.answer() ); - problem.comment( request.comment() ); - problem.mainProblemImageUrl( request.mainProblemImageUrl() ); - problem.mainAnalysisImageUrl( request.mainAnalysisImageUrl() ); - problem.readingTipImageUrl( request.readingTipImageUrl() ); - problem.seniorTipImageUrl( request.seniorTipImageUrl() ); - problem.prescriptionImageUrl( request.prescriptionImageUrl() ); - Set<Long> set = request.conceptTagIds(); - if ( set != null ) { - problem.conceptTagIds( new LinkedHashSet<Long>( set ) ); - } } - problem.id( problemId ); + problem.problemAdminId( problemAdminId ); problem.practiceTestTag( practiceTestTag ); return problem.build(); } @Override - public Problem from(ProblemUpdateRequest request, ProblemId problemId, PracticeTestTag practiceTestTag) { - if ( request == null && problemId == null && practiceTestTag == null ) { + public Problem from(ProblemUpdateRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag) { + if ( request == null && problemAdminId == null && practiceTestTag == null ) { return null; } Problem.ProblemBuilder problem = Problem.builder(); if ( request != null ) { - problem.answer( String.valueOf( request.answer() ) ); - problem.comment( request.comment() ); - problem.mainProblemImageUrl( request.mainProblemImageUrl() ); - problem.mainAnalysisImageUrl( request.mainAnalysisImageUrl() ); - problem.readingTipImageUrl( request.readingTipImageUrl() ); - problem.seniorTipImageUrl( request.seniorTipImageUrl() ); - problem.prescriptionImageUrl( request.prescriptionImageUrl() ); Set<Long> set = request.conceptTagIds(); if ( set != null ) { problem.conceptTagIds( new LinkedHashSet<Long>( set ) ); } + problem.prescriptionImageUrl( request.prescriptionImageUrl() ); + problem.seniorTipImageUrl( request.seniorTipImageUrl() ); + problem.readingTipImageUrl( request.readingTipImageUrl() ); + problem.mainAnalysisImageUrl( request.mainAnalysisImageUrl() ); + problem.mainProblemImageUrl( request.mainProblemImageUrl() ); + problem.answer( request.answer() ); } - problem.id( problemId ); + problem.problemAdminId( problemAdminId ); problem.practiceTestTag( practiceTestTag ); return problem.build(); diff --git a/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java index 3f0c463..e859dd4 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java @@ -33,7 +33,7 @@ public class QProblemSet extends EntityPathBase<ProblemSet> { public final BooleanPath isDeleted = createBoolean("isDeleted"); - public final ListPath<com.moplus.moplus_server.domain.problem.domain.problem.ProblemId, com.moplus.moplus_server.domain.problem.domain.problem.QProblemId> problemIds = this.<com.moplus.moplus_server.domain.problem.domain.problem.ProblemId, com.moplus.moplus_server.domain.problem.domain.problem.QProblemId>createList("problemIds", com.moplus.moplus_server.domain.problem.domain.problem.ProblemId.class, com.moplus.moplus_server.domain.problem.domain.problem.QProblemId.class, PathInits.DIRECT2); + public final ListPath<com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId, com.moplus.moplus_server.domain.problem.domain.problem.QProblemAdminId> problemAdminIds = this.<com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId, com.moplus.moplus_server.domain.problem.domain.problem.QProblemAdminId>createList("problemAdminIds", com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId.class, com.moplus.moplus_server.domain.problem.domain.problem.QProblemAdminId.class, PathInits.DIRECT2); public final QTitle title; diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java index 48d6cfd..353a056 100644 --- a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java @@ -1,12 +1,10 @@ package com.moplus.moplus_server.domain.auth.controller; import com.moplus.moplus_server.domain.auth.dto.request.AdminLoginRequest; +import com.moplus.moplus_server.domain.auth.dto.response.TokenResponse; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.headers.Header; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,23 +16,12 @@ public class AuthController { @Operation(summary = "어드민 로그인", description = "아아디 패스워드 로그인 후 토큰 발급합니다.") - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "로그인 성공", - headers = { - @Header(name = "Authorization", description = "Access Token", schema = @Schema(type = "string")), - @Header(name = "RefreshToken", description = "Refresh Token", schema = @Schema(type = "string")) - } - ), - @ApiResponse(responseCode = "400", description = "잘못된 요청"), - @ApiResponse(responseCode = "401", description = "인증 실패") - }) @PostMapping("/admin/login") - public void adminLogin( + public ResponseEntity<TokenResponse> adminLogin( @RequestBody AdminLoginRequest request ) { // 실제 처리는 Security 필터에서 이루어지며, 이 메서드는 Swagger 명세용입니다. + return null; } - + } diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/TokenResponse.java b/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/TokenResponse.java new file mode 100644 index 0000000..a2be86d --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/TokenResponse.java @@ -0,0 +1,7 @@ +package com.moplus.moplus_server.domain.auth.dto.response; + +public record TokenResponse( + String accessToken, + String refreshToken +) { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java index 6772804..0567525 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java @@ -55,7 +55,7 @@ public ResponseEntity<ProblemGetResponse> updateProblem( @DeleteMapping("/{id}") @Operation(summary = "문항 삭제") - public ResponseEntity<ProblemGetResponse> updateProblem( + public ResponseEntity<Void> updateProblem( @PathVariable("id") String id ) { problemDeleteService.deleteProblem(id); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java index 2cb733a..ac72243 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java @@ -1,6 +1,6 @@ package com.moplus.moplus_server.domain.problem.domain; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; import jakarta.persistence.Column; @@ -17,21 +17,21 @@ public class Answer { @Column(name = "answer") private String value; - public Answer(String value, ProblemType problemType) { - validateByType(value, problemType); + public Answer(String value, AnswerType answerType) { + validateByType(value, answerType); this.value = value; } - private void validateByType(String answer, ProblemType problemType) { + private void validateByType(String answer, AnswerType answerType) { if (answer.isBlank()) { throw new InvalidValueException(ErrorCode.BLANK_INPUT_VALUE); } - if (problemType == ProblemType.MULTIPLE_CHOICE) { + if (answerType == AnswerType.MULTIPLE_CHOICE) { if (!answer.matches("^[1-5]*$")) { throw new InvalidValueException(ErrorCode.INVALID_MULTIPLE_CHOICE_ANSWER); } } - if (problemType == ProblemType.SHORT_NUMBER_ANSWER) { + if (answerType == AnswerType.SHORT_NUMBER_ANSWER) { try { int numericAnswer = Integer.parseInt(answer); if (numericAnswer < 0 || numericAnswer > 999) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java index 2067862..2358435 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.problem.domain.childProblem; import com.moplus.moplus_server.domain.problem.domain.Answer; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.global.common.BaseEntity; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; @@ -38,22 +38,22 @@ public class ChildProblem extends BaseEntity { @Embedded private Answer answer; @Enumerated(EnumType.STRING) - private ProblemType problemType; + private AnswerType answerType; private int sequence; @Builder - public ChildProblem(String imageUrl, ProblemType problemType, String answer, Set<Long> conceptTagIds, + public ChildProblem(String imageUrl, AnswerType answerType, String answer, Set<Long> conceptTagIds, int sequence) { - validateAnswerByType(answer, problemType); + validateAnswerByType(answer, answerType); this.imageUrl = imageUrl; - this.problemType = problemType; - this.answer = new Answer(answer, problemType); + this.answerType = answerType; + this.answer = new Answer(answer, answerType); this.conceptTagIds = conceptTagIds; this.sequence = sequence; } - public void validateAnswerByType(String answer, ProblemType problemType) { - if (this.problemType == ProblemType.MULTIPLE_CHOICE) { + public void validateAnswerByType(String answer, AnswerType answerType) { + if (this.answerType == AnswerType.MULTIPLE_CHOICE) { if (!answer.matches("^[1-5]*$")) { throw new InvalidValueException(ErrorCode.INVALID_MULTIPLE_CHOICE_ANSWER); } @@ -62,7 +62,7 @@ public void validateAnswerByType(String answer, ProblemType problemType) { public void update(ChildProblem input) { this.imageUrl = input.imageUrl; - this.problemType = input.problemType; + this.answerType = input.answerType; this.answer = input.answer; this.conceptTagIds = input.conceptTagIds; this.sequence = input.sequence; diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java index 9dd4f16..75678f1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java @@ -19,7 +19,7 @@ public enum Subject { private final String value; private final int problemCount; private final int perfectScore; - private final int idCode; + private final int code; public static Subject fromValue(String value) { return Arrays.stream(Subject.values()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java new file mode 100644 index 0000000..27d748d --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum AnswerType { + MULTIPLE_CHOICE("객관식"), + SHORT_NUMBER_ANSWER("주관식_숫자"), + SHORT_STRING_ANSWER("주관식_문자"); + + + private final String name; + + public static AnswerType getTypeForProblem(String subject, int number) { + + // 미적분, 기하, 확률과 통계 + if (subject.equals("미적분") || subject.equals("기하") || subject.equals("확률과통계")) { + if ((number >= 1 && number <= 15) || (number >= 23 && number <= 28)) { + return MULTIPLE_CHOICE; + } else if ((number >= 16 && number <= 22) || number == 29 || number == 30) { + return SHORT_NUMBER_ANSWER; + } + } + + // 고1, 고2 + if (subject.equals("고1") || subject.equals("고2")) { + if (number >= 1 && number <= 21) { + return MULTIPLE_CHOICE; + } else if (number >= 22 && number <= 30) { + return SHORT_NUMBER_ANSWER; + } + } + + // 기본값: 객관식 + return MULTIPLE_CHOICE; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index e8c7814..f587281 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -9,9 +9,13 @@ import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; import jakarta.persistence.Embedded; -import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.OrderBy; @@ -29,14 +33,16 @@ @NoArgsConstructor public class Problem extends BaseEntity { - @EmbeddedId - ProblemId id; - + @Embedded + ProblemAdminId problemAdminId; Long practiceTestId; int number; + @Enumerated(EnumType.STRING) + ProblemType problemType; + String title; @Embedded Answer answer; - String comment; + String memo; String mainProblemImageUrl; String mainAnalysisImageUrl; String readingTipImageUrl; @@ -46,9 +52,16 @@ public class Problem extends BaseEntity { @CollectionTable(name = "problem_concept", joinColumns = @JoinColumn(name = "problem_id")) @Column(name = "concept_tag_id") Set<Long> conceptTagIds; - private ProblemType problemType; - private boolean isPublished; - private boolean isVariation; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "problem_id") + private Long id; + + @Enumerated(EnumType.STRING) + private AnswerType answerType; + + private boolean isConfirmed; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name = "problem_id") @@ -56,24 +69,28 @@ public class Problem extends BaseEntity { private List<ChildProblem> childProblems = new ArrayList<>(); @Builder - public Problem(ProblemId id, PracticeTestTag practiceTestTag, int number, String answer, String comment, - String mainProblemImageUrl, - String mainAnalysisImageUrl, String readingTipImageUrl, String seniorTipImageUrl, - String prescriptionImageUrl, Set<Long> conceptTagIds) { - this.id = id; - this.practiceTestId = practiceTestTag.getId(); - this.number = number; - this.comment = comment; - this.mainProblemImageUrl = mainProblemImageUrl; - this.mainAnalysisImageUrl = mainAnalysisImageUrl; - this.readingTipImageUrl = readingTipImageUrl; - this.seniorTipImageUrl = seniorTipImageUrl; + public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType answerType, + Set<Long> conceptTagIds, + String prescriptionImageUrl, String seniorTipImageUrl, String readingTipImageUrl, + String mainAnalysisImageUrl, String mainProblemImageUrl, String memo, String answer, String title, + ProblemType problemType, int number, PracticeTestTag practiceTestTag, + ProblemAdminId problemAdminId) { + this.childProblems = childProblems; + this.isConfirmed = isConfirmed; + this.answerType = AnswerType.getTypeForProblem(practiceTestTag.getSubject().getValue(), number); + this.conceptTagIds = conceptTagIds; this.prescriptionImageUrl = prescriptionImageUrl; - this.problemType = ProblemType.getTypeForProblem(practiceTestTag.getSubject().getValue(), number); - this.answer = new Answer(answer, this.problemType); - this.conceptTagIds = new HashSet<>(conceptTagIds); - this.isPublished = false; - this.isVariation = false; + this.seniorTipImageUrl = seniorTipImageUrl; + this.readingTipImageUrl = readingTipImageUrl; + this.mainAnalysisImageUrl = mainAnalysisImageUrl; + this.mainProblemImageUrl = mainProblemImageUrl; + this.memo = memo; + this.answer = new Answer(answer, this.answerType); + this.title = title; + this.problemType = problemType; + this.number = number; + this.practiceTestId = practiceTestTag.getId(); + this.problemAdminId = problemAdminId; } public String getAnswer() { @@ -90,8 +107,8 @@ public void addChildProblem(List<ChildProblem> inputChildProblems) { public void update(Problem inputProblem) { this.conceptTagIds = new HashSet<>(inputProblem.getConceptTagIds()); this.number = inputProblem.getNumber(); - this.answer = new Answer(inputProblem.getAnswer(), this.problemType); - this.comment = inputProblem.getComment(); + this.answer = new Answer(inputProblem.getAnswer(), this.answerType); + this.memo = inputProblem.getMemo(); this.mainProblemImageUrl = inputProblem.getMainProblemImageUrl(); this.mainAnalysisImageUrl = inputProblem.getMainAnalysisImageUrl(); this.readingTipImageUrl = inputProblem.getReadingTipImageUrl(); @@ -124,7 +141,7 @@ public void deleteChildProblem(List<Long> deleteChildProblems) { public boolean isValid() { return answer != null && !answer.getValue().isEmpty() && practiceTestId != null - && comment != null && !comment.isEmpty() + && memo != null && !memo.isEmpty() && readingTipImageUrl != null && !readingTipImageUrl.isEmpty() && seniorTipImageUrl != null && !seniorTipImageUrl.isEmpty() && prescriptionImageUrl != null && !prescriptionImageUrl.isEmpty() diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java similarity index 70% rename from src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java rename to src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java index 2bc4343..163cd8b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java @@ -9,12 +9,12 @@ @Getter @Embeddable @NoArgsConstructor -public class ProblemId implements Serializable { +public class ProblemAdminId implements Serializable { - @Column(name = "problem_id") + @Column(name = "problem_admin_id") private String id; - public ProblemId(String id) { + public ProblemAdminId(String id) { this.id = id; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java index f3d3e82..c7ad631 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java @@ -15,33 +15,30 @@ public class ProblemIdService { /* 문제 ID 생성 로직 + C : 문제 타입 ( 1: 기출문제, 2: 변형문제, 3: 창작문제 ) + S : ( 1: 고1, 2: 고2, 3: 미적분, 4: 기하, 5: 확률과 통계, 6: 가형, 7: 나형 ) YY: 년도 (두 자리) MM: 월 (두 자리) NN : 번호 (01~99) - AA : 영역 ( 01: 수학, 02: 영어, 03: 국어, 04: 사회, 05: 과학 ) - S : ( 1: 고1, 2: 고2, 3: 미적분, 4: 기하, 5: 확률과 통계, 6: 가형, 7: 나형 ) - C : 변형 여부 ( 0: 기본, 1: 변형 ) - XXX : 3자리 구분 숫자 + XX : 2자리 sequence 숫자 */ - public ProblemId nextId(int number, PracticeTestTag practiceTestTag) { + public ProblemAdminId nextId(int number, PracticeTestTag practiceTestTag, ProblemType problemType) { - int DEFAULT_AREA = 1; //현재 영역은 수학밖에 없음 - int subject = practiceTestTag.getSubject().getIdCode(); // AA (과목) + int problemTypeCode = problemType.getCode(); // C (문제 타입) + int subject = practiceTestTag.getSubject().getCode(); // S (과목) int year = practiceTestTag.getYear() % 100; // YY (두 자리 연도) int month = practiceTestTag.getMonth(); // MM (두 자리 월) - int DEFAULT_MODIFIED = 0; // 변형 여부 (0: 기본, 1: 변형) String generatedId; int sequence; // 중복되지 않는 ID 찾을 때까지 반복 do { - sequence = SEQUENCE.getAndIncrement() % 1000; // 000~999 순환 - generatedId = String.format("%02d%02d%02d%02d%d%d%03d", - year, month, number, DEFAULT_AREA, - subject, DEFAULT_MODIFIED, sequence); - } while (problemRepository.existsById(new ProblemId(generatedId))); // ID가 이미 존재하면 재생성 + sequence = SEQUENCE.getAndIncrement() % 100; // 000~999 순환 + generatedId = String.format("%d%d%02d%02d%02d%02d", + problemTypeCode, subject, year, month, number, sequence); + } while (problemRepository.existsById(new ProblemAdminId(generatedId))); // ID가 이미 존재하면 재생성 - return new ProblemId(generatedId); + return new ProblemAdminId(generatedId); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java index 2410bcf..e02d101 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java @@ -1,37 +1,16 @@ package com.moplus.moplus_server.domain.problem.domain.problem; +import lombok.Getter; import lombok.RequiredArgsConstructor; +@Getter @RequiredArgsConstructor public enum ProblemType { - MULTIPLE_CHOICE("객관식"), - SHORT_NUMBER_ANSWER("주관식_숫자"), - SHORT_STRING_ANSWER("주관식_문자"); + GICHUL_PROBLEM("기출문제", 1), + VARIANT_PROBLEM("변형문제", 2), + CREATION_PROBLEM("창작문제", 3); private final String name; - - public static ProblemType getTypeForProblem(String subject, int number) { - - // 미적분, 기하, 확률과 통계 - if (subject.equals("미적분") || subject.equals("기하") || subject.equals("확률과통계")) { - if ((number >= 1 && number <= 15) || (number >= 23 && number <= 28)) { - return MULTIPLE_CHOICE; - } else if ((number >= 16 && number <= 22) || number == 29 || number == 30) { - return SHORT_NUMBER_ANSWER; - } - } - - // 고1, 고2 - if (subject.equals("고1") || subject.equals("고2")) { - if (number >= 1 && number <= 21) { - return MULTIPLE_CHOICE; - } else if (number >= 22 && number <= 30) { - return SHORT_NUMBER_ANSWER; - } - } - - // 기본값: 객관식 - return MULTIPLE_CHOICE; - } + private final int code; } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java index 1b42975..fc3e302 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java @@ -1,11 +1,11 @@ package com.moplus.moplus_server.domain.problem.dto.request; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import java.util.Set; public record ChildProblemPostRequest( String imageUrl, - ProblemType problemType, + AnswerType answerType, String answer, Set<Long> conceptTagIds, int sequence diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java index d080b17..19ba8ff 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java @@ -1,6 +1,6 @@ package com.moplus.moplus_server.domain.problem.dto.request; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import io.swagger.v3.oas.annotations.media.Schema; import java.util.Set; @@ -8,7 +8,7 @@ public record ChildProblemUpdateRequest( @Schema(description = "새로 생성되는 새끼문항은 빈 값입니다.") Long id, String imageUrl, - ProblemType problemType, + AnswerType answerType, String answer, Set<Long> conceptTagIds, int sequence diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java index d6e5821..3746ed5 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java @@ -2,36 +2,20 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; -import java.util.List; -import java.util.Set; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; public record ProblemPostRequest( - Set<Long> conceptTagIds, + ProblemType problemType, Long practiceTestId, - int number, - String answer, - String comment, - String mainProblemImageUrl, - String mainAnalysisImageUrl, - String readingTipImageUrl, - String seniorTipImageUrl, - String prescriptionImageUrl, - List<ChildProblemPostRequest> childProblems + int number ) { - public Problem toEntity(PracticeTestTag practiceTestTag, ProblemId problemId) { + public Problem toEntity(PracticeTestTag practiceTestTag, ProblemAdminId problemAdminId) { return Problem.builder() - .id(problemId) - .conceptTagIds(conceptTagIds) + .problemAdminId(problemAdminId) .practiceTestTag(practiceTestTag) .number(number) - .answer(answer) - .comment(comment) - .mainProblemImageUrl(mainProblemImageUrl) - .mainAnalysisImageUrl(mainAnalysisImageUrl) - .readingTipImageUrl(readingTipImageUrl) - .seniorTipImageUrl(seniorTipImageUrl) - .prescriptionImageUrl(prescriptionImageUrl) + .problemType(problemType) .build(); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java index 9e04f0d..6ea8677 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java @@ -5,7 +5,7 @@ public record ProblemUpdateRequest( Set<Long> conceptTagIds, - int answer, + String answer, String comment, String mainProblemImageUrl, String mainAnalysisImageUrl, diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java index 1641d2c..ae7552a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.problem.dto.response; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import java.util.Set; import lombok.Builder; @@ -9,7 +9,7 @@ public record ChildProblemGetResponse( Long childProblemId, String imageUrl, - ProblemType problemType, + AnswerType answerType, String answer, Set<Long> conceptTagIds ) { @@ -18,7 +18,7 @@ public static ChildProblemGetResponse of(ChildProblem childProblem) { return ChildProblemGetResponse.builder() .childProblemId(childProblem.getId()) .imageUrl(childProblem.getImageUrl()) - .problemType(childProblem.getProblemType()) + .answerType(childProblem.getAnswerType()) .answer(childProblem.getAnswer()) .conceptTagIds(childProblem.getConceptTagIds()) .build(); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java index 1947067..50e90b8 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java @@ -1,6 +1,8 @@ package com.moplus.moplus_server.domain.problem.dto.response; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import java.util.List; import java.util.Set; import lombok.Builder; @@ -13,6 +15,8 @@ public record ProblemGetResponse( int number, String answer, String comment, + ProblemType problemType, + AnswerType answerType, String mainProblemImageUrl, String mainAnalysisImageUrl, String readingTipImageUrl, @@ -28,7 +32,9 @@ public static ProblemGetResponse of(Problem problem) { .practiceTestId(problem.getPracticeTestId()) .number(problem.getNumber()) .answer(problem.getAnswer()) - .comment(problem.getComment()) + .comment(problem.getMemo()) + .problemType(problem.getProblemType()) + .answerType(problem.getAnswerType()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) .mainAnalysisImageUrl(problem.getMainAnalysisImageUrl()) .readingTipImageUrl(problem.getReadingTipImageUrl()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java index a45c8f4..50d7132 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java @@ -1,29 +1,21 @@ package com.moplus.moplus_server.domain.problem.repository; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; -import com.moplus.moplus_server.global.error.exception.AlreadyExistException; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; -public interface ProblemRepository extends JpaRepository<Problem, ProblemId> { +public interface ProblemRepository extends JpaRepository<Problem, ProblemAdminId> { - boolean existsByPracticeTestIdAndNumber(Long practiceTestId, int number); - default void existsByPracticeTestIdAndNumberOrThrow(Long practiceTestId, int number) { - if (existsByPracticeTestIdAndNumber(practiceTestId, number)) { - throw new AlreadyExistException(ErrorCode.PROBLEM_ALREADY_EXIST); - } - } - - default void existsByIdElseThrow(ProblemId problemId) { - if (!existsById(problemId)) { + default void existsByIdElseThrow(ProblemAdminId problemAdminId) { + if (!existsById(problemAdminId)) { throw new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND); } } - default Problem findByIdElseThrow(ProblemId problemId) { - return findById(problemId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); + default Problem findByIdElseThrow(ProblemAdminId problemAdminId) { + return findById(problemAdminId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java index 1bb193f..03b6e53 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java @@ -1,6 +1,6 @@ package com.moplus.moplus_server.domain.problem.service; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -14,7 +14,7 @@ public class ProblemDeleteService { @Transactional public void deleteProblem(String problemId) { - problemRepository.existsByIdElseThrow(new ProblemId(problemId)); - problemRepository.deleteById(new ProblemId(problemId)); + problemRepository.existsByIdElseThrow(new ProblemAdminId(problemId)); + problemRepository.deleteById(new ProblemAdminId(problemId)); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java index 65dccf1..6abd479 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.problem.service; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import lombok.RequiredArgsConstructor; @@ -16,7 +16,7 @@ public class ProblemGetService { @Transactional(readOnly = true) public ProblemGetResponse getProblem(String problemId) { - Problem problem = problemRepository.findByIdElseThrow(new ProblemId(problemId)); + Problem problem = problemRepository.findByIdElseThrow(new ProblemAdminId(problemId)); return ProblemGetResponse.of(problem); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java index 6db9ddc..b661d89 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java @@ -1,17 +1,13 @@ package com.moplus.moplus_server.domain.problem.service; -import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; -import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemIdService; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; -import com.moplus.moplus_server.domain.problem.service.mapper.ChildProblemMapper; import com.moplus.moplus_server.domain.problem.service.mapper.ProblemMapper; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,25 +18,17 @@ public class ProblemSaveService { private final ProblemRepository problemRepository; private final PracticeTestTagRepository practiceTestRepository; - private final ConceptTagRepository conceptTagRepository; private final ProblemIdService problemIdService; private final ProblemMapper problemMapper; - private final ChildProblemMapper childProblemMapper; @Transactional - public ProblemId createProblem(ProblemPostRequest request) { + public ProblemAdminId createProblem(ProblemPostRequest request) { PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); - problemRepository.existsByPracticeTestIdAndNumberOrThrow(practiceTestTag.getId(), request.number()); - conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); - ProblemId problemId = problemIdService.nextId(request.number(), practiceTestTag); - Problem problem = problemMapper.from(request, problemId, practiceTestTag); + ProblemAdminId problemAdminId = problemIdService.nextId(request.number(), practiceTestTag, + request.problemType()); + Problem problem = problemMapper.from(request, problemAdminId, practiceTestTag); - List<ChildProblem> childProblems = request.childProblems().stream() - .map(childProblemMapper::from) - .toList(); - problem.addChildProblem(childProblems); - - return problemRepository.save(problem).getId(); + return problemRepository.save(problem).getProblemAdminId(); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java index a7f0678..90a5e62 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java @@ -4,7 +4,7 @@ import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; @@ -32,9 +32,9 @@ public class ProblemUpdateService { @Transactional public ProblemGetResponse updateProblem(String problemId, ProblemUpdateRequest request) { conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); - Problem problem = problemRepository.findByIdElseThrow(new ProblemId(problemId)); + Problem problem = problemRepository.findByIdElseThrow(new ProblemAdminId(problemId)); PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(problem.getPracticeTestId()); - Problem inputProblem = problemMapper.from(request, problem.getId(), practiceTestTag); + Problem inputProblem = problemMapper.from(request, problem.getProblemAdminId(), practiceTestTag); problem.update(inputProblem); problem.deleteChildProblem(request.deleteChildProblems()); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java index f1244a2..5497c41 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java @@ -2,7 +2,7 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import org.mapstruct.Mapper; @@ -13,14 +13,14 @@ public interface ProblemMapper { @Mappings({ - @Mapping(target = "id", source = "problemId"), + @Mapping(target = "problemAdminId", source = "problemAdminId"), @Mapping(target = "practiceTestTag", source = "practiceTestTag"), }) - Problem from(ProblemPostRequest request, ProblemId problemId, PracticeTestTag practiceTestTag); + Problem from(ProblemPostRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag); @Mappings({ - @Mapping(target = "id", source = "problemId"), + @Mapping(target = "problemAdminId", source = "problemAdminId"), @Mapping(target = "practiceTestTag", source = "practiceTestTag"), }) - Problem from(ProblemUpdateRequest request, ProblemId problemId, PracticeTestTag practiceTestTag); + Problem from(ProblemUpdateRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index bcd4d16..268b392 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.problemset.domain; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.global.common.BaseEntity; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; @@ -43,20 +43,20 @@ public class ProblemSet extends BaseEntity { @ElementCollection @CollectionTable(name = "problem_set_problems", joinColumns = @JoinColumn(name = "problem_set_id")) - @Column(name = "problem_id") + @Column(name = "problem_admin_id") @OrderColumn(name = "sequence") - private List<ProblemId> problemIds = new ArrayList<>(); + private List<ProblemAdminId> problemAdminIds = new ArrayList<>(); @Builder - public ProblemSet(String title, List<ProblemId> problemIds) { + public ProblemSet(String title, List<ProblemAdminId> problemAdminIds) { this.title = new Title(title); this.isDeleted = false; this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED; - this.problemIds = problemIds; + this.problemAdminIds = problemAdminIds; } - public void updateProblemOrder(List<ProblemId> newProblems) { - this.problemIds = new ArrayList<>(newProblems); + public void updateProblemOrder(List<ProblemAdminId> newProblems) { + this.problemAdminIds = new ArrayList<>(newProblems); } public void deleteProblemSet() { @@ -64,10 +64,10 @@ public void deleteProblemSet() { } public void toggleConfirm(List<Problem> problems) { - if(this.confirmStatus == ProblemSetConfirmStatus.NOT_CONFIRMED){ + if (this.confirmStatus == ProblemSetConfirmStatus.NOT_CONFIRMED) { List<String> invalidProblemIds = problems.stream() .filter(problem -> !problem.isValid()) - .map(problem -> problem.getId().getId()) + .map(problem -> problem.getProblemAdminId().getId()) .toList(); if (!invalidProblemIds.isEmpty()) { String message = ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage() + @@ -78,8 +78,8 @@ public void toggleConfirm(List<Problem> problems) { this.confirmStatus = this.confirmStatus.toggle(); } - public void updateProblemSet(String title, List<ProblemId> newProblems) { + public void updateProblemSet(String title, List<ProblemAdminId> newProblems) { this.title = new Title(title); - this.problemIds = newProblems; + this.problemAdminIds = newProblems; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java index eb50f4d..0de61e7 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java @@ -1,6 +1,6 @@ package com.moplus.moplus_server.domain.problemset.dto.request; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import java.util.List; @@ -8,10 +8,10 @@ public record ProblemSetPostRequest( String problemSetTitle, List<String> problems ) { - public ProblemSet toEntity(List<ProblemId> problemIdList) { + public ProblemSet toEntity(List<ProblemAdminId> problemAdminIdList) { return ProblemSet.builder() .title(this.problemSetTitle) - .problemIds(problemIdList) + .problemAdminIds(problemAdminIdList) .build(); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java index b563593..024d9ab 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -9,7 +9,7 @@ public record ProblemSummaryResponse( String problemId, int number, String practiceTestName, - String comment, + String memo, String mainProblemImageUrl, List<String> tagNames ) { @@ -18,7 +18,7 @@ public static ProblemSummaryResponse of(Problem problem, String practiceTestName return ProblemSummaryResponse.builder() .problemId(problem.getId().toString()) .number(problem.getNumber()) - .comment(problem.getComment()) + .memo(problem.getMemo()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) .practiceTestName(practiceTestName) .tagNames(tagNames) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index 9bf6d22..8cccfba 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -22,10 +22,11 @@ public class ProblemSetSearchRepositoryCustom { private final JPAQueryFactory queryFactory; - public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String problemTitle, List<String> conceptTagNames) { + public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String problemTitle, + List<String> conceptTagNames) { return queryFactory .from(problemSet) - .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 + .leftJoin(problem).on(problem.problemAdminId.in(problemSet.problemAdminIds)) // 문제 세트 내 포함된 문항과 조인 .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( @@ -48,10 +49,11 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p )); } - public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, String problemTitle, List<String> conceptTagNames) { + public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, String problemTitle, + List<String> conceptTagNames) { return queryFactory .from(problemSet) - .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 + .leftJoin(problem).on(problem.problemAdminId.in(problemSet.problemAdminIds)) // 문제 세트 내 포함된 문항과 조인 .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( @@ -76,11 +78,12 @@ public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, S } private BooleanExpression containsProblemSetTitle(String problemSetTitle) { - return (problemSetTitle == null || problemSetTitle.isEmpty()) ? null : problemSet.title.value.containsIgnoreCase(problemSetTitle); + return (problemSetTitle == null || problemSetTitle.isEmpty()) ? null + : problemSet.title.value.containsIgnoreCase(problemSetTitle); } private BooleanExpression containsProblemTitle(String problemTitle) { - return (problemTitle == null || problemTitle.isEmpty()) ? null : problem.comment.containsIgnoreCase(problemTitle); + return (problemTitle == null || problemTitle.isEmpty()) ? null : problem.memo.containsIgnoreCase(problemTitle); } private BooleanExpression containsConceptTagNames(List<String> conceptTagNames) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java index 3392a8d..f236958 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java @@ -4,7 +4,7 @@ import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; @@ -39,8 +39,8 @@ public ProblemSetGetResponse getProblemSet(Long problemSetId) { .orElse(null); List<ProblemSummaryResponse> problemSummaries = new ArrayList<>(); - for (ProblemId problemId : problemSet.getProblemIds()) { - Problem problem = problemRepository.findByIdElseThrow(problemId); + for (ProblemAdminId problemAdminId : problemSet.getProblemAdminIds()) { + Problem problem = problemRepository.findByIdElseThrow(problemAdminId); PracticeTestTag practiceTestTag = practiceTestTagRepository.findByIdElseThrow(problem.getPracticeTestId()); List<String> tagNames = conceptTagRepository.findAllByIdsElseThrow(problem.getConceptTagIds()) .stream() diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java index c363815..57627ae 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -1,6 +1,6 @@ package com.moplus.moplus_server.domain.problemset.service; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; @@ -27,15 +27,15 @@ public Long createProblemSet(ProblemSetPostRequest request) { } // 문제 ID 리스트를 ProblemId 객체로 변환 - List<ProblemId> problemIdList = request.problems().stream() - .map(ProblemId::new) + List<ProblemAdminId> problemAdminIdList = request.problems().stream() + .map(ProblemAdminId::new) .toList(); // 모든 문항이 DB에 존재하는지 검증 - problemIdList.forEach(problemRepository::findByIdElseThrow); + problemAdminIdList.forEach(problemRepository::findByIdElseThrow); // ProblemSet 생성 - ProblemSet problemSet = request.toEntity(problemIdList); + ProblemSet problemSet = request.toEntity(problemAdminIdList); return problemSetRepository.save(problemSet).getId(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java index 92134ac..3bb8fc9 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.problemset.service; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; @@ -29,11 +29,11 @@ public void reorderProblems(Long problemSetId, ProblemReorderRequest request) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); // 기존 문항 ID 리스트 업데이트 (순서 반영) - List<ProblemId> updatedProblemIds = request.newProblems().stream() - .map(ProblemId::new) + List<ProblemAdminId> updatedProblemAdminIds = request.newProblems().stream() + .map(ProblemAdminId::new) .collect(Collectors.toList()); - problemSet.updateProblemOrder(updatedProblemIds); + problemSet.updateProblemOrder(updatedProblemAdminIds); } @Transactional @@ -46,20 +46,20 @@ public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) } // 문항 리스트 검증 - List<ProblemId> problemIdList = request.problems().stream() - .map(ProblemId::new) + List<ProblemAdminId> problemAdminIdList = request.problems().stream() + .map(ProblemAdminId::new) .collect(Collectors.toList()); - problemIdList.forEach(problemRepository::findByIdElseThrow); + problemAdminIdList.forEach(problemRepository::findByIdElseThrow); - problemSet.updateProblemSet(request.problemSetTitle(), problemIdList); + problemSet.updateProblemSet(request.problemSetTitle(), problemAdminIdList); } @Transactional public ProblemSetConfirmStatus toggleConfirmProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); List<Problem> problems = new ArrayList<>(); - for (ProblemId problemId : problemSet.getProblemIds()) { - Problem problem = problemRepository.findByIdElseThrow(problemId); + for (ProblemAdminId problemAdminId : problemSet.getProblemAdminIds()) { + Problem problem = problemRepository.findByIdElseThrow(problemAdminId); problems.add(problem); } problemSet.toggleConfirm(problems); diff --git a/src/main/resources/templates/answerInputForm.html b/src/main/resources/templates/answerInputForm.html index 6fd3d02..08e8d99 100644 --- a/src/main/resources/templates/answerInputForm.html +++ b/src/main/resources/templates/answerInputForm.html @@ -8,7 +8,7 @@ <script> function validatePoints() { let totalPoints = 0; - const radios = document.querySelectorAll('input[problemType="radio"]:checked'); + const radios = document.querySelectorAll('input[answerType="radio"]:checked'); radios.forEach(radio => { if (radio.name.startsWith('point_')) { @@ -30,7 +30,7 @@ <h2>답안 입력</h2> <form method="post" onsubmit="return validatePoints()" th:action="${problemForTests != null ? '/admin/practiceTests/submitAnswers/' + practiceTestId : '/admin/practiceTests/submitAnswers'}"> - <input name="practiceTestId" problemType="hidden" th:value="${practiceTestId}"> + <input answerType="hidden" name="practiceTestId" th:value="${practiceTestId}"> <table> <thead> <tr> @@ -46,38 +46,42 @@ <h2>답안 입력</h2> <td> <!-- subject가 미적분, 기하, 확률과통계일 경우의 배점 로직 --> <th:block th:if="${subject eq '미적분' || subject eq '기하' || subject eq '확률과통계'}"> - <input problemType="radio" th:checked="${#arrays.contains(new int[]{1, 2, 23}, i)}" + <input answerType="radio" th:checked="${#arrays.contains(new int[]{1, 2, 23}, i)}" th:id="'point_' + ${i} + '_2'" th:name="'point_' + ${i}" value="2"> <label th:for="'point_' + ${i} + '_2'">2점</label> - <input problemType="radio" - th:checked="${#arrays.contains(new int[]{3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 24, 25, 26, 27}, i)}" th:id="'point_' + ${i} + '_3'" th:name="'point_' + ${i}" + <input answerType="radio" + th:checked="${#arrays.contains(new int[]{3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 24, 25, 26, 27}, i)}" + th:id="'point_' + ${i} + '_3'" th:name="'point_' + ${i}" value="3"> <label th:for="'point_' + ${i} + '_3'">3점</label> - <input problemType="radio" - th:checked="${#arrays.contains(new int[]{9, 10, 11, 12, 13, 14, 15, 20, 21, 22, 28, 29, 30}, i)}" th:id="'point_' + ${i} + '_4'" th:name="'point_' + ${i}" + <input answerType="radio" + th:checked="${#arrays.contains(new int[]{9, 10, 11, 12, 13, 14, 15, 20, 21, 22, 28, 29, 30}, i)}" + th:id="'point_' + ${i} + '_4'" th:name="'point_' + ${i}" value="4"> <label th:for="'point_' + ${i} + '_4'">4점</label> </th:block> <!-- subject가 고1, 고2일 경우의 배점 로직 --> <th:block th:if="${subject eq '고1' || subject eq '고2'}"> - <input problemType="radio" th:checked="${#arrays.contains(new int[]{1, 2, 3}, i)}" + <input answerType="radio" th:checked="${#arrays.contains(new int[]{1, 2, 3}, i)}" th:id="'point_' + ${i} + '_2'" th:name="'point_' + ${i}" value="2"> <label th:for="'point_' + ${i} + '_2'">2점</label> - <input problemType="radio" - th:checked="${#arrays.contains(new int[]{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 23, 24, 25}, i)}" th:id="'point_' + ${i} + '_3'" th:name="'point_' + ${i}" + <input answerType="radio" + th:checked="${#arrays.contains(new int[]{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 23, 24, 25}, i)}" + th:id="'point_' + ${i} + '_3'" th:name="'point_' + ${i}" value="3"> <label th:for="'point_' + ${i} + '_3'">3점</label> - <input problemType="radio" - th:checked="${#arrays.contains(new int[]{14, 15, 16, 17, 18, 19, 20, 21, 26, 27, 28, 29, 30}, i)}" th:id="'point_' + ${i} + '_4'" th:name="'point_' + ${i}" + <input answerType="radio" + th:checked="${#arrays.contains(new int[]{14, 15, 16, 17, 18, 19, 20, 21, 26, 27, 28, 29, 30}, i)}" + th:id="'point_' + ${i} + '_4'" th:name="'point_' + ${i}" value="4"> <label th:for="'point_' + ${i} + '_4'">4점</label> </th:block> </td> - <input problemType="hidden" th:name="'answerFormat_' + ${i}" + <input answerType="hidden" th:name="'answerFormat_' + ${i}" th:value="${hasShortAnswer && i > 22 ? '단답식' : '객관식'}"> <!-- 문제 형식이 단답식인 경우 --> @@ -85,30 +89,35 @@ <h2>답안 입력</h2> <td th:if="${hasShortAnswer}"> <div th:if="${i <= 15 || (i >=23 && i<=28)}"> <!-- 첫 22개의 객관식 문항 --> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '1'}" th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '1'}" + th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" value="1"> <label th:for="'answer_' + ${i} + '_1'">1</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '2'}" th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '2'}" + th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" value="2"> <label th:for="'answer_' + ${i} + '_2'">2</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '3'}" th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '3'}" + th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" value="3"> <label th:for="'answer_' + ${i} + '_3'">3</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '4'}" th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '4'}" + th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" value="4"> <label th:for="'answer_' + ${i} + '_4'">4</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '5'}" th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '5'}" + th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" value="5"> <label th:for="'answer_' + ${i} + '_5'">5</label> </div> <div th:if="${(i >= 16 && i<= 22) || i == 29 || i == 30}"> <!-- 단답식 문항 --> - <input problemType="text" + <input answerType="text" th:name="'answer_' + ${i}" th:value="${problemForTests != null && problemForTests[i-1] != null ? problemForTests[i-1].answer : ''}"> </div> @@ -119,30 +128,35 @@ <h2>답안 입력</h2> <td th:if="${hasShortAnswer}"> <div th:if="${i <= 21}"> <!-- 첫 21개의 객관식 문항 --> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '1'}" th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '1'}" + th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" value="1"> <label th:for="'answer_' + ${i} + '_1'">1</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '2'}" th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '2'}" + th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" value="2"> <label th:for="'answer_' + ${i} + '_2'">2</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '3'}" th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '3'}" + th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" value="3"> <label th:for="'answer_' + ${i} + '_3'">3</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '4'}" th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '4'}" + th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" value="4"> <label th:for="'answer_' + ${i} + '_4'">4</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '5'}" th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '5'}" + th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" value="5"> <label th:for="'answer_' + ${i} + '_5'">5</label> </div> <div th:if="${i >= 22 && i <= 30}"> <!-- 단답식 문항 --> - <input problemType="text" + <input answerType="text" th:name="'answer_' + ${i}" th:value="${problemForTests != null && problemForTests[i-1] != null ? problemForTests[i-1].answer : ''}"> </div> @@ -151,31 +165,36 @@ <h2>답안 입력</h2> <!-- 객관식만 있을 때 --> <td th:unless="${hasShortAnswer}"> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '1'}" th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '1'}" + th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" value="1"> <label th:for="'answer_' + ${i} + '_1'">1</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '2'}" th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '2'}" + th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" value="2"> <label th:for="'answer_' + ${i} + '_2'">2</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '3'}" th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '3'}" + th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" value="3"> <label th:for="'answer_' + ${i} + '_3'">3</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '4'}" th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '4'}" + th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" value="4"> <label th:for="'answer_' + ${i} + '_4'">4</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '5'}" th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '5'}" + th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" value="5"> <label th:for="'answer_' + ${i} + '_5'">5</label> </td> <!-- 정답률 입력 필드 --> <td> - <input placeholder="정답률 입력" problemType="text" style="width: 150px;" + <input answerType="text" placeholder="정답률 입력" style="width: 150px;" th:id="'correctRate_' + ${i}" th:name="'correctRate_' + ${i}" th:value="${problemForTests != null && problemForTests[i-1] != null ? problemForTests[i-1].correctRate : ''}"> @@ -183,7 +202,7 @@ <h2>답안 입력</h2> </tr> </tbody> </table> - <button problemType="submit">입력 완료</button> + <button answerType="submit">입력 완료</button> </form> </div> <script th:inline="javascript"> diff --git a/src/main/resources/templates/imageUploadPage.html b/src/main/resources/templates/imageUploadPage.html index 210cdd1..59fe602 100644 --- a/src/main/resources/templates/imageUploadPage.html +++ b/src/main/resources/templates/imageUploadPage.html @@ -22,7 +22,7 @@ <h2>문제 이미지 업로드</h2> <!-- 반복문으로 문제 리스트 출력 --> <th:block th:each="problemForTest : ${problemImageRequests}"> <tr> - <td th:text="${problemForTest.problemId}">문제 ID</td> + <td th:text="${problemForTest.problemAdminId}">문제 ID</td> <td th:text="${problemForTest.problemNumber}">문제 번호</td> <td> <!-- 이미지 URL이 있는 경우 이미지를 표시 --> @@ -34,10 +34,10 @@ <h2>문제 이미지 업로드</h2> <td> <!-- 업로드 버튼 --> <form enctype="multipart/form-data" method="post" - th:action="'/admin/practiceTests/uploadImage/' + ${problemForTest.problemId}"> - <input accept="image/*" name="image" problemType="file"> - <input name="practiceTestId" problemType="hidden" th:value="${practiceTestId}"> - <button class="upload-button" problemType="submit">업로드</button> + th:action="'/admin/practiceTests/uploadImage/' + ${problemForTest.problemAdminId}"> + <input accept="image/*" answerType="file" name="image"> + <input answerType="hidden" name="practiceTestId" th:value="${practiceTestId}"> + <button answerType="submit" class="upload-button">업로드</button> </form> </td> </tr> @@ -47,7 +47,7 @@ <h2>문제 이미지 업로드</h2> <!-- 완료 버튼 --> <div class="complete-button-container"> <form action="/practiceTests" method="get"> - <button class="complete-button" problemType="submit">완료</button> + <button answerType="submit" class="complete-button">완료</button> </form> </div> </div> diff --git a/src/main/resources/templates/practiceTestList.html b/src/main/resources/templates/practiceTestList.html index f3ef96f..dab424d 100644 --- a/src/main/resources/templates/practiceTestList.html +++ b/src/main/resources/templates/practiceTestList.html @@ -34,8 +34,8 @@ <body> <div class="container"> <div class="search-bar"> - <input id="search-input" onkeyup="filterTests()" placeholder="검색" problemType="text"> - <button problemType="button"> + <input answerType="text" id="search-input" onkeyup="filterTests()" placeholder="검색"> + <button answerType="button"> <img alt="검색" src="/images/search_icon.png"> </button> </div> @@ -57,8 +57,8 @@ </a> </div> <form class="delete-form" method="post" th:action="@{'/admin/practiceTests/' + ${test.id} + '/delete'}"> - <input name="_method" problemType="hidden" value="delete"> - <button class="delete-test-button" onclick="confirmDeletion(event)" problemType="button">삭제</button> + <input answerType="hidden" name="_method" value="delete"> + <button answerType="button" class="delete-test-button" onclick="confirmDeletion(event)">삭제</button> </form> </div> </li> diff --git a/src/main/resources/templates/testInputForm.html b/src/main/resources/templates/testInputForm.html index 1666a8a..c49358d 100644 --- a/src/main/resources/templates/testInputForm.html +++ b/src/main/resources/templates/testInputForm.html @@ -19,7 +19,7 @@ font-weight: bold; } - input[problemType="text"] { + input[answerType="text"] { width: 100%; padding: 8px; box-sizing: border-box; @@ -48,7 +48,7 @@ } /* Radio 버튼 숨기기 */ - .subject-item input[problemType="radio"] { + .subject-item input[answerType="radio"] { display: none; } @@ -64,7 +64,7 @@ } /* 선택된 라디오 버튼 스타일 */ - .subject-item input[problemType="radio"]:checked + label { + .subject-item input[answerType="radio"]:checked + label { background-color: #f79a3e; color: white; border-color: #f47c20; @@ -106,25 +106,26 @@ <h1>Practice Test 등록</h1> <form method="post" - th:action="${practiceTestRequest.id != null ? '/admin/practiceTests/submit/' + practiceTestRequest.id : '/admin/practiceTests/submit'}" th:object="${practiceTestRequest}"> + th:action="${practiceTestRequest.id != null ? '/admin/practiceTests/submit/' + practiceTestRequest.id : '/admin/practiceTests/submit'}" + th:object="${practiceTestRequest}"> <div class="form-group"> <label for="name">이름</label> - <input id="name" placeholder="이름" problemType="text" th:field="*{name}"> + <input answerType="text" id="name" placeholder="이름" th:field="*{name}"> </div> <div class="form-group"> <label for="round">회차</label> - <input id="round" placeholder="회차" problemType="text" th:field="*{round}"> + <input answerType="text" id="round" placeholder="회차" th:field="*{round}"> </div> <div class="form-group"> <label for="provider">출제기관</label> - <input id="provider" placeholder="출제기관" problemType="text" th:field="*{provider}"> + <input answerType="text" id="provider" placeholder="출제기관" th:field="*{provider}"> </div> <div class="form-group"> <label for="provider">출제 연도</label> - <input id="publicationYear" placeholder="출제 연도" problemType="text" th:field="*{publicationYear}"> + <input answerType="text" id="publicationYear" placeholder="출제 연도" th:field="*{publicationYear}"> </div> <div class="form-group"> @@ -132,7 +133,7 @@ <h1>Practice Test 등록</h1> <div class="subject-container"> <div class="subject-item" th:each="subject, iterStat : ${subjects}"> <!-- 고유한 id와 for 값을 설정 --> - <input problemType="radio" th:field="*{subject}" th:id="'subject' + ${iterStat.index}" + <input answerType="radio" th:field="*{subject}" th:id="'subject' + ${iterStat.index}" th:onchange="'showTable(' + ${iterStat.index} + ')'" th:value="${subject}"/> <label th:for="'subject' + ${iterStat.index}" th:text="${subject.getValue()}"></label> @@ -150,9 +151,10 @@ <h3 th:text="${subject.getValue()}"></h3> <div th:each="ratingTable, ratingTableStat : ${practiceTestRequest.ratingTables}"> <!-- Provider 이름 표시 행 --> <th colspan="4"> - <input placeholder="Rating Provider" - problemType="text" - th:field="*{ratingTables[__${ratingTableStat.index}__].ratingProvider}" th:value="${ratingTable.ratingProvider}"/> + <input answerType="text" + placeholder="Rating Provider" + th:field="*{ratingTables[__${ratingTableStat.index}__].ratingProvider}" + th:value="${ratingTable.ratingProvider}"/> </th> <tr> <th>등급</th> @@ -163,19 +165,23 @@ <h3 th:text="${subject.getValue()}"></h3> <tr th:each="ratingRow, ratingRowStat : ${ratingTable.ratingRows}" th:if="${ratingTable.ratingRows != null and !#lists.isEmpty(ratingTable.ratingRows)}"> <td> - <input problemType="text" + <input answerType="text" readonly - th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].rating}" th:value="${ratingRow.rating}"/> + th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].rating}" + th:value="${ratingRow.rating}"/> + </td> + <td><input answerType="text" + placeholder="원점수" + th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].rawScores}"> + </td> + <td><input answerType="text" + placeholder="표준점수" + th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].standardScores}"> + </td> + <td><input answerType="text" + placeholder="백분위" + th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].percentiles}"> </td> - <td><input placeholder="원점수" - problemType="text" - th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].rawScores}"></td> - <td><input placeholder="표준점수" - problemType="text" - th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].standardScores}"></td> - <td><input placeholder="백분위" - problemType="text" - th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].percentiles}"></td> </tr> </div> </tbody> @@ -184,7 +190,7 @@ <h3 th:text="${subject.getValue()}"></h3> </div> </div> - <button problemType="submit">선택완료</button> + <button answerType="submit">선택완료</button> </form> <script> @@ -209,7 +215,7 @@ <h3 th:text="${subject.getValue()}"></h3> // 페이지가 로드되었을 때 선택된 과목이 있으면 해당 테이블 표시 document.addEventListener('DOMContentLoaded', function () { - const selectedRadio = document.querySelector('.subject-item input[problemType="radio"]:checked'); + const selectedRadio = document.querySelector('.subject-item input[answerType="radio"]:checked'); if (selectedRadio) { showTable(selectedRadio.id.replace('subject', '')); } diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java similarity index 84% rename from src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java rename to src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java index 629e128..8b78a53 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java @@ -19,7 +19,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class ProblemIdServiceTest { +class ProblemAdminIdServiceTest { @Mock private ProblemRepository problemRepository; @@ -41,10 +41,10 @@ void setUp() { void nextId_정상생성_및_중복확인() { // given int 문제번호 = 20; - when(problemRepository.existsById(any(ProblemId.class))).thenReturn(false); // 중복 없음 + when(problemRepository.existsById(any(ProblemAdminId.class))).thenReturn(false); // 중복 없음 // when - ProblemId generatedId = problemIdService.nextId(문제번호, practiceTestTag); + ProblemAdminId generatedId = problemIdService.nextId(문제번호, practiceTestTag); // then assertThat(generatedId).isNotNull(); @@ -52,7 +52,7 @@ void setUp() { assertThat(generatedId.getId()).startsWith("2405200120"); // 문제 ID 중복 확인을 위해 existsById 호출 확인 - verify(problemRepository, atLeastOnce()).existsById(any(ProblemId.class)); + verify(problemRepository, atLeastOnce()).existsById(any(ProblemAdminId.class)); } @@ -60,12 +60,12 @@ void setUp() { void nextId_중복발생시_다시_생성() { // given int 문제번호 = 2; - when(problemRepository.existsById(any(ProblemId.class))) + when(problemRepository.existsById(any(ProblemAdminId.class))) .thenReturn(true) // 첫 번째 생성된 ID는 중복됨 .thenReturn(false); // 두 번째는 중복 없음 // when - ProblemId generatedId = problemIdService.nextId(문제번호, practiceTestTag); + ProblemAdminId generatedId = problemIdService.nextId(문제번호, practiceTestTag); // then assertThat(generatedId).isNotNull(); @@ -73,6 +73,6 @@ void setUp() { assertThat(generatedId.getId()).startsWith("2405020120"); // 중복된 ID가 나왔으므로 existsById가 최소 두 번 이상 호출되었는지 확인 - verify(problemRepository, atLeast(2)).existsById(any(ProblemId.class)); + verify(problemRepository, atLeast(2)).existsById(any(ProblemAdminId.class)); } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java index 63abbe6..f19a71c 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java @@ -3,9 +3,9 @@ import static org.assertj.core.api.Assertions.assertThat; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; @@ -39,16 +39,16 @@ class ProblemSaveServiceTest { void setUp() { // 🔹 1. 일부러 순서를 뒤죽박죽으로 설정한 문제 ChildProblemPostRequest childProblem1 = new ChildProblemPostRequest( - "child1.png", ProblemType.SHORT_STRING_ANSWER, "정답1", Set.of(3L, 4L), 3 + "child1.png", AnswerType.SHORT_STRING_ANSWER, "정답1", Set.of(3L, 4L), 3 ); ChildProblemPostRequest childProblem2 = new ChildProblemPostRequest( - "child2.png", ProblemType.MULTIPLE_CHOICE, "1", Set.of(5L, 6L), 1 + "child2.png", AnswerType.MULTIPLE_CHOICE, "1", Set.of(5L, 6L), 1 ); ChildProblemPostRequest childProblem3 = new ChildProblemPostRequest( - "child3.png", ProblemType.MULTIPLE_CHOICE, "2", Set.of(3L, 4L), 0 + "child3.png", AnswerType.MULTIPLE_CHOICE, "2", Set.of(3L, 4L), 0 ); ChildProblemPostRequest childProblem4 = new ChildProblemPostRequest( - "child4.png", ProblemType.SHORT_NUMBER_ANSWER, "0", Set.of(1L, 2L), 2 + "child4.png", AnswerType.SHORT_NUMBER_ANSWER, "0", Set.of(1L, 2L), 2 ); problemPostRequestOutOfOrder = new ProblemPostRequest( @@ -86,13 +86,13 @@ void setUp() { void 정상동작() { // when - ProblemId createdProblemId = problemSaveService.createProblem(problemPostRequestInOrder); + ProblemAdminId createdProblemAdminId = problemSaveService.createProblem(problemPostRequestInOrder); // then - assertThat(createdProblemId).isNotNull(); - assertThat(createdProblemId.getId()).startsWith("2405200120"); // ID 앞부분 확인 + assertThat(createdProblemAdminId).isNotNull(); + assertThat(createdProblemAdminId.getId()).startsWith("2405200120"); // ID 앞부분 확인 - Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemId); + Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemAdminId); // 모든 자식 문제의 conceptTagIds가 부모 문제의 conceptTagIds에 포함되는지 검증 Set<Long> problemTags = savedProblem.getConceptTagIds(); @@ -114,14 +114,14 @@ void setUp() { @Test void 자식문제_올바른_순서_저장() { // when - ProblemId createdProblemId = problemSaveService.createProblem(problemPostRequestOutOfOrder); + ProblemAdminId createdProblemAdminId = problemSaveService.createProblem(problemPostRequestOutOfOrder); // then - assertThat(createdProblemId).isNotNull(); - assertThat(createdProblemId.getId()).startsWith("2405210120"); // ID 앞부분 확인 + assertThat(createdProblemAdminId).isNotNull(); + assertThat(createdProblemAdminId.getId()).startsWith("2405210120"); // ID 앞부분 확인 // 저장된 문제 조회 - Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemId); + Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemAdminId); // ✅ 모든 자식 문제의 conceptTagIds가 부모 문제의 conceptTagIds에 포함되는지 검증 Set<Long> problemTags = savedProblem.getConceptTagIds(); diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java index 6d59ad5..1c9252a 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java @@ -3,9 +3,9 @@ import static org.assertj.core.api.Assertions.assertThat; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; @@ -33,18 +33,18 @@ class ProblemUpdateServiceTest { @Autowired private ProblemRepository problemRepository; - private ProblemId problemId; + private ProblemAdminId problemAdminId; private ProblemUpdateRequest problemUpdateRequest; @BeforeEach void setUp() { - problemId = new ProblemId("240520012001"); + problemAdminId = new ProblemAdminId("240520012001"); // 🔹 새 자식 문제 추가 ChildProblemUpdateRequest newChildProblem = new ChildProblemUpdateRequest( null, "newChild.png", - ProblemType.SHORT_STRING_ANSWER, + AnswerType.SHORT_STRING_ANSWER, "새로운 정답", Set.of(1L, 2L), 1 @@ -54,7 +54,7 @@ void setUp() { ChildProblemUpdateRequest updateChildProblem = new ChildProblemUpdateRequest( 1L, // 기존 자식 문제 ID "updatedChild.png", - ProblemType.MULTIPLE_CHOICE, + AnswerType.MULTIPLE_CHOICE, "2", Set.of(2L, 3L), 0 @@ -80,7 +80,7 @@ void setUp() { @Test void 문제_업데이트_정상동작() { // when - ProblemGetResponse response = problemUpdateService.updateProblem(problemId.getId(), + ProblemGetResponse response = problemUpdateService.updateProblem(problemAdminId.getId(), problemUpdateRequest); // then @@ -88,7 +88,7 @@ void setUp() { assertThat(response.comment()).isEqualTo("수정된 설명"); // ✅ 설명이 변경되었는지 검증 assertThat(response.mainProblemImageUrl()).isEqualTo("updatedMainProblem.png"); // ✅ 이미지 URL 변경 확인 - Problem updatedProblem = problemRepository.findByIdElseThrow(problemId); + Problem updatedProblem = problemRepository.findByIdElseThrow(problemAdminId); // ✅ 자식 문제 개수 검증 List<ChildProblem> childProblems = updatedProblem.getChildProblems(); diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index d682201..1beeee6 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -59,10 +59,10 @@ void setUp() { assertThat(savedProblemSet).isNotNull(); assertThat(savedProblemSet.getTitle().getValue()).isEqualTo("초기 문항세트"); - assertThat(savedProblemSet.getProblemIds()).hasSize(3); - assertThat(savedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001001"); - assertThat(savedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001002"); - assertThat(savedProblemSet.getProblemIds().get(2).getId()).isEqualTo("24052001003"); + assertThat(savedProblemSet.getProblemAdminIds()).hasSize(3); + assertThat(savedProblemSet.getProblemAdminIds().get(0).getId()).isEqualTo("24052001001"); + assertThat(savedProblemSet.getProblemAdminIds().get(1).getId()).isEqualTo("24052001002"); + assertThat(savedProblemSet.getProblemAdminIds().get(2).getId()).isEqualTo("24052001003"); } @Test @@ -80,9 +80,9 @@ void setUp() { ProblemSet updatedProblemSet = problemSetRepository.findById(problemSetId) .orElseThrow(() -> new IllegalArgumentException("문항세트를 찾을 수 없습니다.")); - assertThat(updatedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001003"); - assertThat(updatedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001001"); - assertThat(updatedProblemSet.getProblemIds().get(2).getId()).isEqualTo("24052001002"); + assertThat(updatedProblemSet.getProblemAdminIds().get(0).getId()).isEqualTo("24052001003"); + assertThat(updatedProblemSet.getProblemAdminIds().get(1).getId()).isEqualTo("24052001001"); + assertThat(updatedProblemSet.getProblemAdminIds().get(2).getId()).isEqualTo("24052001002"); } @Test @@ -101,9 +101,9 @@ void setUp() { ProblemSet updatedProblemSet = problemSetRepository.findByIdElseThrow(problemSetId); assertThat(updatedProblemSet.getTitle().getValue()).isEqualTo("업데이트된 문항세트"); - assertThat(updatedProblemSet.getProblemIds()).hasSize(2); - assertThat(updatedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001002"); - assertThat(updatedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001003"); + assertThat(updatedProblemSet.getProblemAdminIds()).hasSize(2); + assertThat(updatedProblemSet.getProblemAdminIds().get(0).getId()).isEqualTo("24052001002"); + assertThat(updatedProblemSet.getProblemAdminIds().get(1).getId()).isEqualTo("24052001003"); } @Test @@ -112,8 +112,10 @@ void setUp() { Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); // when - ProblemSetConfirmStatus firstToggleStatus = problemSetUpdateService.toggleConfirmProblemSet(problemSetId); // CONFIRMED - ProblemSetConfirmStatus secondToggleStatus = problemSetUpdateService.toggleConfirmProblemSet(problemSetId); // NOT_CONFIRMED + ProblemSetConfirmStatus firstToggleStatus = problemSetUpdateService.toggleConfirmProblemSet( + problemSetId); // CONFIRMED + ProblemSetConfirmStatus secondToggleStatus = problemSetUpdateService.toggleConfirmProblemSet( + problemSetId); // NOT_CONFIRMED // then assertThat(firstToggleStatus).isEqualTo(ProblemSetConfirmStatus.CONFIRMED); // 첫 번째 호출 후 컨펌 상태 확인 diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java index 85e7e40..386779b 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java @@ -4,7 +4,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; @@ -61,9 +61,9 @@ void setUp() { ); // 문제 저장 - ProblemId createdProblemId = problemRepository.save( + ProblemAdminId createdProblemAdminId = problemRepository.save( new Problem( - new ProblemId("24052001001"), + new ProblemAdminId("24052001001"), practiceTestTagRepository.findByIdElseThrow(1L), 1, "1", @@ -77,7 +77,7 @@ void setUp() { ) ).getId(); - savedProblem = problemRepository.findByIdElseThrow(createdProblemId); + savedProblem = problemRepository.findByIdElseThrow(createdProblemAdminId); // 문항세트 저장 savedProblemSet = problemSetRepository.save( @@ -112,7 +112,7 @@ void setUp() { // given: 여러 개의 문제를 저장 Problem savedProblem2 = problemRepository.save( new Problem( - new ProblemId("24052001002"), + new ProblemAdminId("24052001002"), practiceTestTagRepository.findByIdElseThrow(1L), 2, "2", From 8f9cd9f40976eb2962574d1ee28876c3a940cfbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 13 Feb 2025 05:09:09 +0900 Subject: [PATCH 076/115] =?UTF-8?q?[feat/#34]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=82=AC=ED=95=AD=20=EB=AA=A8=EB=91=90=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=86=B5=EA=B3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problem/domain/problem/QDifficulty.java | 37 ++++ .../problem/domain/problem/QProblem.java | 10 +- .../domain/problem/domain/problem/QTitle.java | 37 ++++ .../service/mapper/ProblemMapperImpl.java | 16 +- .../domain/problemset/domain/QProblemSet.java | 2 +- .../domain/concept/domain/ConceptTag.java | 3 +- .../problem/controller/ProblemController.java | 13 +- .../domain/problem/domain/Answer.java | 6 +- .../domain/childProblem/ChildProblem.java | 3 +- .../domain/practiceTest/PracticeTestTag.java | 3 +- .../problem/domain/problem/Difficulty.java | 32 ++++ .../problem/domain/problem/Problem.java | 67 +++++-- .../domain/problem/ProblemAdminId.java | 3 +- ...ervice.java => ProblemAdminIdService.java} | 4 +- .../domain/problem/domain/problem/Title.java | 26 +++ .../dto/request/ProblemPostRequest.java | 5 +- .../dto/request/ProblemUpdateRequest.java | 13 +- .../dto/response/ProblemGetResponse.java | 16 +- .../problem/repository/ProblemRepository.java | 11 +- .../ProblemSearchRepositoryCustom.java | 13 +- .../converter/StringListConverter.java | 41 +++++ .../problem/service/ProblemDeleteService.java | 7 +- .../problem/service/ProblemGetService.java | 5 +- .../problem/service/ProblemSaveService.java | 12 +- .../problem/service/ProblemUpdateService.java | 14 +- .../domain/problemset/domain/ProblemSet.java | 17 +- .../dto/request/ProblemReorderRequest.java | 2 +- .../dto/request/ProblemSetPostRequest.java | 7 +- .../dto/request/ProblemSetUpdateRequest.java | 2 +- .../dto/response/ProblemSummaryResponse.java | 2 +- .../ProblemSetSearchRepositoryCustom.java | 4 +- .../service/ProblemSetGetService.java | 5 +- .../service/ProblemSetSaveService.java | 11 +- .../service/ProblemSetUpdateService.java | 23 +-- .../global/error/GlobalExceptionHandler.java | 15 ++ .../global/error/exception/ErrorCode.java | 1 + .../problem/ProblemAdminIdServiceTest.java | 25 +-- .../ProblemSearchRepositoryCustomTest.java | 12 +- .../service/ProblemSaveServiceTest.java | 148 ++++----------- .../service/ProblemUpdateServiceTest.java | 172 +++++++++++++----- .../problemset/ProblemSetServiceTest.java | 25 +-- .../ProblemSetSearchRepositoryCustomTest.java | 2 +- .../service/ProblemSetGetServiceTest.java | 71 +------- src/test/resources/insert-problem-set.sql | 14 +- src/test/resources/insert-problem.sql | 63 ++++--- src/test/resources/insert-problem2.sql | 109 ++++++++--- src/test/resources/practice-test-tag.sql | 3 +- 47 files changed, 708 insertions(+), 424 deletions(-) create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QDifficulty.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QTitle.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java rename src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/{ProblemIdService.java => ProblemAdminIdService.java} (91%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Title.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QDifficulty.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QDifficulty.java new file mode 100644 index 0000000..ccd4d1b --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QDifficulty.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QDifficulty is a Querydsl query type for Difficulty + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QDifficulty extends BeanPath<Difficulty> { + + private static final long serialVersionUID = 175172936L; + + public static final QDifficulty difficulty1 = new QDifficulty("difficulty1"); + + public final NumberPath<Integer> difficulty = createNumber("difficulty", Integer.class); + + public QDifficulty(String variable) { + super(Difficulty.class, forVariable(variable)); + } + + public QDifficulty(Path<? extends Difficulty> path) { + super(path.getType(), path.getMetadata()); + } + + public QDifficulty(PathMetadata metadata) { + super(Difficulty.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java index 41612a2..74bade6 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java @@ -35,12 +35,16 @@ public class QProblem extends EntityPathBase<Problem> { //inherited public final DateTimePath<java.time.LocalDateTime> createdDate = _super.createdDate; + public final QDifficulty difficulty; + public final NumberPath<Long> id = createNumber("id", Long.class); public final BooleanPath isConfirmed = createBoolean("isConfirmed"); public final StringPath mainAnalysisImageUrl = createString("mainAnalysisImageUrl"); + public final StringPath mainHandwritingExplanationImageUrl = createString("mainHandwritingExplanationImageUrl"); + public final StringPath mainProblemImageUrl = createString("mainProblemImageUrl"); public final StringPath memo = createString("memo"); @@ -49,7 +53,7 @@ public class QProblem extends EntityPathBase<Problem> { public final NumberPath<Long> practiceTestId = createNumber("practiceTestId", Long.class); - public final StringPath prescriptionImageUrl = createString("prescriptionImageUrl"); + public final ListPath<String, StringPath> prescriptionImageUrls = this.<String, StringPath>createList("prescriptionImageUrls", String.class, StringPath.class, PathInits.DIRECT2); public final QProblemAdminId problemAdminId; @@ -59,7 +63,7 @@ public class QProblem extends EntityPathBase<Problem> { public final StringPath seniorTipImageUrl = createString("seniorTipImageUrl"); - public final StringPath title = createString("title"); + public final QTitle title; //inherited public final DateTimePath<java.time.LocalDateTime> updatedDate = _super.updatedDate; @@ -83,7 +87,9 @@ public QProblem(PathMetadata metadata, PathInits inits) { public QProblem(Class<? extends Problem> type, PathMetadata metadata, PathInits inits) { super(type, metadata, inits); this.answer = inits.isInitialized("answer") ? new com.moplus.moplus_server.domain.problem.domain.QAnswer(forProperty("answer")) : null; + this.difficulty = inits.isInitialized("difficulty") ? new QDifficulty(forProperty("difficulty")) : null; this.problemAdminId = inits.isInitialized("problemAdminId") ? new QProblemAdminId(forProperty("problemAdminId")) : null; + this.title = inits.isInitialized("title") ? new QTitle(forProperty("title")) : null; } } diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QTitle.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QTitle.java new file mode 100644 index 0000000..4c4cdd1 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QTitle.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QTitle is a Querydsl query type for Title + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QTitle extends BeanPath<Title> { + + private static final long serialVersionUID = 42281131L; + + public static final QTitle title1 = new QTitle("title1"); + + public final StringPath title = createString("title"); + + public QTitle(String variable) { + super(Title.class, forVariable(variable)); + } + + public QTitle(Path<? extends Title> path) { + super(path.getType(), path.getMetadata()); + } + + public QTitle(PathMetadata metadata) { + super(Title.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index e8b0d28..cf20a4a 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -5,14 +5,16 @@ import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; +import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import javax.annotation.processing.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-12T02:55:03+0900", + date = "2025-02-13T05:08:28+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component @@ -45,16 +47,26 @@ public Problem from(ProblemUpdateRequest request, ProblemAdminId problemAdminId, Problem.ProblemBuilder problem = Problem.builder(); if ( request != null ) { + problem.answerType( request.answerType() ); Set<Long> set = request.conceptTagIds(); if ( set != null ) { problem.conceptTagIds( new LinkedHashSet<Long>( set ) ); } - problem.prescriptionImageUrl( request.prescriptionImageUrl() ); + problem.difficulty( request.difficulty() ); + problem.mainHandwritingExplanationImageUrl( request.mainHandwritingExplanationImageUrl() ); + List<String> list = request.prescriptionImageUrls(); + if ( list != null ) { + problem.prescriptionImageUrls( new ArrayList<String>( list ) ); + } problem.seniorTipImageUrl( request.seniorTipImageUrl() ); problem.readingTipImageUrl( request.readingTipImageUrl() ); problem.mainAnalysisImageUrl( request.mainAnalysisImageUrl() ); problem.mainProblemImageUrl( request.mainProblemImageUrl() ); + problem.memo( request.memo() ); problem.answer( request.answer() ); + problem.title( request.title() ); + problem.problemType( request.problemType() ); + problem.number( request.number() ); } problem.problemAdminId( problemAdminId ); problem.practiceTestTag( practiceTestTag ); diff --git a/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java index e859dd4..dc1cf58 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java @@ -33,7 +33,7 @@ public class QProblemSet extends EntityPathBase<ProblemSet> { public final BooleanPath isDeleted = createBoolean("isDeleted"); - public final ListPath<com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId, com.moplus.moplus_server.domain.problem.domain.problem.QProblemAdminId> problemAdminIds = this.<com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId, com.moplus.moplus_server.domain.problem.domain.problem.QProblemAdminId>createList("problemAdminIds", com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId.class, com.moplus.moplus_server.domain.problem.domain.problem.QProblemAdminId.class, PathInits.DIRECT2); + public final ListPath<Long, NumberPath<Long>> problemIds = this.<Long, NumberPath<Long>>createList("problemIds", Long.class, NumberPath.class, PathInits.DIRECT2); public final QTitle title; diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java b/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java index 86e49c2..90d4c83 100644 --- a/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java +++ b/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java @@ -6,12 +6,13 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ConceptTag extends BaseEntity { @Id diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java index 0567525..4d7f2fb 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java @@ -8,6 +8,7 @@ import com.moplus.moplus_server.domain.problem.service.ProblemSaveService; import com.moplus.moplus_server.domain.problem.service.ProblemUpdateService; import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -31,23 +32,23 @@ public class ProblemController { @GetMapping("/{id}") @Operation(summary = "문항 조회", description = "문항를 조회합니다.") public ResponseEntity<ProblemGetResponse> getProblem( - @PathVariable("id") String id + @PathVariable("id") Long id ) { return ResponseEntity.ok(problemGetService.getProblem(id)); } @PostMapping("") @Operation(summary = "문항 생성", description = "문제를 생성합니다. 새끼 문항은 list 순서대로 sequence를 저장합니다.") - public ResponseEntity<String> createProblem( - @RequestBody ProblemPostRequest request + public ResponseEntity<Long> createProblem( + @Valid @RequestBody ProblemPostRequest request ) { - return ResponseEntity.ok(problemSaveService.createProblem(request).toString()); + return ResponseEntity.ok(problemSaveService.createProblem(request)); } @PostMapping("/{id}") @Operation(summary = "문항 업데이트", description = "문제를 업데이트합니다. 문항 번호, 모의고사는 수정할 수 없습니다. 새로 추가되는 새끼문항 id는 빈 값입니다.") public ResponseEntity<ProblemGetResponse> updateProblem( - @PathVariable("id") String id, + @PathVariable("id") Long id, @RequestBody ProblemUpdateRequest request ) { return ResponseEntity.ok(problemUpdateService.updateProblem(id, request)); @@ -56,7 +57,7 @@ public ResponseEntity<ProblemGetResponse> updateProblem( @DeleteMapping("/{id}") @Operation(summary = "문항 삭제") public ResponseEntity<Void> updateProblem( - @PathVariable("id") String id + @PathVariable("id") Long id ) { problemDeleteService.deleteProblem(id); return ResponseEntity.ok().body(null); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java index ac72243..79ec350 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java @@ -18,14 +18,14 @@ public class Answer { private String value; public Answer(String value, AnswerType answerType) { + if (value == null) { + return; + } validateByType(value, answerType); this.value = value; } private void validateByType(String answer, AnswerType answerType) { - if (answer.isBlank()) { - throw new InvalidValueException(ErrorCode.BLANK_INPUT_VALUE); - } if (answerType == AnswerType.MULTIPLE_CHOICE) { if (!answer.matches("^[1-5]*$")) { throw new InvalidValueException(ErrorCode.INVALID_MULTIPLE_CHOICE_ANSWER); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java index 2358435..f42c2d7 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java @@ -17,13 +17,14 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import java.util.Set; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ChildProblem extends BaseEntity { @Id diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java index b21f78b..6f57f26 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java @@ -8,13 +8,14 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity @Table(name = "practice_test_tag") -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class PracticeTestTag { @Id diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java new file mode 100644 index 0000000..7cbcf54 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java @@ -0,0 +1,32 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Difficulty { + + @Column(name = "difficulty") + private Integer difficulty; + + public Difficulty(Integer difficulty) { + if (difficulty == null) { + return; + } + validate(difficulty); + this.difficulty = difficulty; + } + + private void validate(int difficulty) { + if (difficulty < 1 || difficulty > 10) { + throw new InvalidValueException(ErrorCode.INVALID_DIFFICULTY); + } + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index f587281..bc3f96b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -3,10 +3,12 @@ import com.moplus.moplus_server.domain.problem.domain.Answer; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; +import com.moplus.moplus_server.domain.problem.repository.converter.StringListConverter; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.ElementCollection; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -24,13 +26,14 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Problem extends BaseEntity { @Embedded @@ -39,15 +42,23 @@ public class Problem extends BaseEntity { int number; @Enumerated(EnumType.STRING) ProblemType problemType; - String title; + @Embedded + Title title; @Embedded Answer answer; + @Embedded + Difficulty difficulty; + String memo; String mainProblemImageUrl; String mainAnalysisImageUrl; + String mainHandwritingExplanationImageUrl; String readingTipImageUrl; String seniorTipImageUrl; - String prescriptionImageUrl; + + @Convert(converter = StringListConverter.class) + @Column(columnDefinition = "TEXT") + List<String> prescriptionImageUrls; @ElementCollection @CollectionTable(name = "problem_concept", joinColumns = @JoinColumn(name = "problem_id")) @Column(name = "concept_tag_id") @@ -70,23 +81,25 @@ public class Problem extends BaseEntity { @Builder public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType answerType, - Set<Long> conceptTagIds, - String prescriptionImageUrl, String seniorTipImageUrl, String readingTipImageUrl, + Set<Long> conceptTagIds, Integer difficulty, String mainHandwritingExplanationImageUrl, + List<String> prescriptionImageUrls, String seniorTipImageUrl, String readingTipImageUrl, String mainAnalysisImageUrl, String mainProblemImageUrl, String memo, String answer, String title, ProblemType problemType, int number, PracticeTestTag practiceTestTag, ProblemAdminId problemAdminId) { this.childProblems = childProblems; this.isConfirmed = isConfirmed; - this.answerType = AnswerType.getTypeForProblem(practiceTestTag.getSubject().getValue(), number); + this.answerType = answerType; this.conceptTagIds = conceptTagIds; - this.prescriptionImageUrl = prescriptionImageUrl; + this.mainHandwritingExplanationImageUrl = mainHandwritingExplanationImageUrl; + this.prescriptionImageUrls = prescriptionImageUrls; this.seniorTipImageUrl = seniorTipImageUrl; this.readingTipImageUrl = readingTipImageUrl; this.mainAnalysisImageUrl = mainAnalysisImageUrl; this.mainProblemImageUrl = mainProblemImageUrl; + this.difficulty = new Difficulty(difficulty); this.memo = memo; this.answer = new Answer(answer, this.answerType); - this.title = title; + this.title = new Title(title); this.problemType = problemType; this.number = number; this.practiceTestId = practiceTestTag.getId(); @@ -105,15 +118,23 @@ public void addChildProblem(List<ChildProblem> inputChildProblems) { } public void update(Problem inputProblem) { - this.conceptTagIds = new HashSet<>(inputProblem.getConceptTagIds()); + this.problemAdminId = inputProblem.getProblemAdminId(); + this.practiceTestId = inputProblem.getPracticeTestId(); this.number = inputProblem.getNumber(); - this.answer = new Answer(inputProblem.getAnswer(), this.answerType); + this.problemType = inputProblem.getProblemType(); + this.title = new Title(inputProblem.getTitle()); + this.answer = new Answer(inputProblem.getAnswer(), inputProblem.getAnswerType()); + this.difficulty = new Difficulty(inputProblem.getDifficulty()); + this.conceptTagIds = new HashSet<>(inputProblem.getConceptTagIds()); this.memo = inputProblem.getMemo(); this.mainProblemImageUrl = inputProblem.getMainProblemImageUrl(); this.mainAnalysisImageUrl = inputProblem.getMainAnalysisImageUrl(); + this.mainHandwritingExplanationImageUrl = inputProblem.getMainHandwritingExplanationImageUrl(); // 추가 this.readingTipImageUrl = inputProblem.getReadingTipImageUrl(); this.seniorTipImageUrl = inputProblem.getSeniorTipImageUrl(); - this.prescriptionImageUrl = inputProblem.getPrescriptionImageUrl(); + this.prescriptionImageUrls = inputProblem.getPrescriptionImageUrls(); + this.answerType = inputProblem.getAnswerType(); + this.isConfirmed = inputProblem.isConfirmed(); } public void updateChildProblem(List<ChildProblem> inputChildProblems) { @@ -139,13 +160,29 @@ public void deleteChildProblem(List<Long> deleteChildProblems) { } public boolean isValid() { - return answer != null && !answer.getValue().isEmpty() + return problemAdminId != null && practiceTestId != null + && problemType != null + && title != null && !title.getTitle().isEmpty() + && answer != null && !answer.getValue().isEmpty() + && difficulty != null && difficulty.getDifficulty() != null && memo != null && !memo.isEmpty() + && mainProblemImageUrl != null && !mainProblemImageUrl.isEmpty() + && mainAnalysisImageUrl != null && !mainAnalysisImageUrl.isEmpty() + && mainHandwritingExplanationImageUrl != null && !mainHandwritingExplanationImageUrl.isEmpty() && readingTipImageUrl != null && !readingTipImageUrl.isEmpty() && seniorTipImageUrl != null && !seniorTipImageUrl.isEmpty() - && prescriptionImageUrl != null && !prescriptionImageUrl.isEmpty() - && mainProblemImageUrl != null && !mainProblemImageUrl.isEmpty() - && mainAnalysisImageUrl != null && !mainAnalysisImageUrl.isEmpty(); + && prescriptionImageUrls != null && !prescriptionImageUrls.isEmpty() + && prescriptionImageUrls.stream().allMatch(url -> url != null && !url.isEmpty()) + && answerType != null + && conceptTagIds != null && !conceptTagIds.isEmpty(); + } + + public String getTitle() { + return title.getTitle(); + } + + public Integer getDifficulty() { + return difficulty.getDifficulty(); } } \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java index 163cd8b..a30bb59 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java @@ -3,12 +3,13 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import java.io.Serializable; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Embeddable -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ProblemAdminId implements Serializable { @Column(name = "problem_admin_id") diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java similarity index 91% rename from src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java rename to src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java index c7ad631..8c61f9f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java @@ -8,7 +8,7 @@ @Service @RequiredArgsConstructor -public class ProblemIdService { +public class ProblemAdminIdService { private static final AtomicInteger SEQUENCE = new AtomicInteger(1); // XXX 값 증가를 위한 카운터 private final ProblemRepository problemRepository; @@ -37,7 +37,7 @@ public ProblemAdminId nextId(int number, PracticeTestTag practiceTestTag, Proble sequence = SEQUENCE.getAndIncrement() % 100; // 000~999 순환 generatedId = String.format("%d%d%02d%02d%02d%02d", problemTypeCode, subject, year, month, number, sequence); - } while (problemRepository.existsById(new ProblemAdminId(generatedId))); // ID가 이미 존재하면 재생성 + } while (problemRepository.existsByProblemAdminId(new ProblemAdminId(generatedId))); // ID가 이미 존재하면 재생성 return new ProblemAdminId(generatedId); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Title.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Title.java new file mode 100644 index 0000000..2aa77dc --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Title.java @@ -0,0 +1,26 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Title { + + private static final String DEFAULT_TITLE = "제목 없음"; + + @Column(name = "title") + private String title; + + public Title(String title) { + this.title = verifyTitle(title); + } + + private static String verifyTitle(String title) { + return (title == null || title.trim().isEmpty()) ? DEFAULT_TITLE : title; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java index 3746ed5..c909206 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java @@ -4,8 +4,10 @@ import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import jakarta.validation.constraints.NotNull; public record ProblemPostRequest( + @NotNull(message = "문제 유형은 필수입니다") ProblemType problemType, Long practiceTestId, int number @@ -15,7 +17,8 @@ public Problem toEntity(PracticeTestTag practiceTestTag, ProblemAdminId problemA .problemAdminId(problemAdminId) .practiceTestTag(practiceTestTag) .number(number) + .title("") .problemType(problemType) .build(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java index 6ea8677..1dcab23 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java @@ -1,17 +1,26 @@ package com.moplus.moplus_server.domain.problem.dto.request; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import java.util.List; import java.util.Set; public record ProblemUpdateRequest( + ProblemType problemType, + Long practiceTestId, + int number, Set<Long> conceptTagIds, String answer, - String comment, + String title, + Integer difficulty, + String memo, String mainProblemImageUrl, String mainAnalysisImageUrl, + String mainHandwritingExplanationImageUrl, String readingTipImageUrl, String seniorTipImageUrl, - String prescriptionImageUrl, + List<String> prescriptionImageUrls, + AnswerType answerType, List<ChildProblemUpdateRequest> updateChildProblems, List<Long> deleteChildProblems ) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java index 50e90b8..a39c30f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java @@ -13,33 +13,39 @@ public record ProblemGetResponse( Set<Long> conceptTagIds, Long practiceTestId, int number, + Integer difficulty, + String title, String answer, - String comment, + String memo, ProblemType problemType, AnswerType answerType, String mainProblemImageUrl, + String mainHandwritingExplanationImageUrl, String mainAnalysisImageUrl, String readingTipImageUrl, String seniorTipImageUrl, - String prescriptionImageUrl, + List<String> prescriptionImageUrls, List<ChildProblemGetResponse> childProblems ) { public static ProblemGetResponse of(Problem problem) { return ProblemGetResponse.builder() - .problemId(problem.getId().toString()) + .problemId(problem.getProblemAdminId().getId()) .conceptTagIds(problem.getConceptTagIds()) .practiceTestId(problem.getPracticeTestId()) .number(problem.getNumber()) .answer(problem.getAnswer()) - .comment(problem.getMemo()) + .title(problem.getTitle()) + .difficulty(problem.getDifficulty()) + .memo(problem.getMemo()) .problemType(problem.getProblemType()) .answerType(problem.getAnswerType()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) + .mainHandwritingExplanationImageUrl(problem.getMainHandwritingExplanationImageUrl()) .mainAnalysisImageUrl(problem.getMainAnalysisImageUrl()) .readingTipImageUrl(problem.getReadingTipImageUrl()) .seniorTipImageUrl(problem.getSeniorTipImageUrl()) - .prescriptionImageUrl(problem.getPrescriptionImageUrl()) + .prescriptionImageUrls(problem.getPrescriptionImageUrls()) .childProblems(problem.getChildProblems().stream() .map(ChildProblemGetResponse::of) .toList()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java index 50d7132..04e7d94 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java @@ -6,16 +6,17 @@ import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; -public interface ProblemRepository extends JpaRepository<Problem, ProblemAdminId> { +public interface ProblemRepository extends JpaRepository<Problem, Long> { + boolean existsByProblemAdminId(ProblemAdminId problemAdminId); - default void existsByIdElseThrow(ProblemAdminId problemAdminId) { - if (!existsById(problemAdminId)) { + default void existsByIdElseThrow(Long id) { + if (!existsById(id)) { throw new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND); } } - default Problem findByIdElseThrow(ProblemAdminId problemAdminId) { - return findById(problemAdminId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); + default Problem findByIdElseThrow(Long id) { + return findById(id).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java index 97af1a4..b2ff859 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java @@ -21,7 +21,7 @@ public class ProblemSearchRepositoryCustom { public List<ProblemSearchGetResponse> search(String problemId, String comment, List<Long> conceptTagIds) { return queryFactory - .select(problem.id.id, problem.comment, problem.mainProblemImageUrl) + .select(problem.problemAdminId.id, problem.memo, problem.mainProblemImageUrl) .from(problem) .where( containsProblemId(problemId), @@ -30,10 +30,10 @@ public List<ProblemSearchGetResponse> search(String problemId, String comment, L ) .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)).fetchJoin() .distinct() - .transform(GroupBy.groupBy(problem.id.id).list( + .transform(GroupBy.groupBy(problem.id).list( Projections.constructor(ProblemSearchGetResponse.class, - problem.id.id, - problem.comment, + problem.problemAdminId.id, + problem.memo, problem.mainProblemImageUrl, GroupBy.set( Projections.constructor(ConceptTagSearchResponse.class, @@ -47,7 +47,8 @@ public List<ProblemSearchGetResponse> search(String problemId, String comment, L //problemId 일부 포함 검색 private BooleanExpression containsProblemId(String problemId) { - return (problemId == null || problemId.isEmpty()) ? null : problem.id.id.containsIgnoreCase(problemId); + return (problemId == null || problemId.isEmpty()) ? null + : problem.problemAdminId.id.containsIgnoreCase(problemId); } //name 조건 (포함 검색) @@ -55,7 +56,7 @@ private BooleanExpression containsName(String comment) { if (comment == null || comment.trim().isEmpty()) { return null; } - return problem.comment.containsIgnoreCase(comment.trim()); + return problem.memo.containsIgnoreCase(comment.trim()); } //conceptTagIds 조건 (하나라도 포함되면 조회) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java new file mode 100644 index 0000000..9ca476b --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java @@ -0,0 +1,41 @@ +package com.moplus.moplus_server.domain.problem.repository.converter; + +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Converter +public class StringListConverter implements AttributeConverter<List<String>, String> { + + private static final String DELIMITER = ", "; + + @Override + public String convertToDatabaseColumn(List<String> attribute) { + try { + if (attribute == null || attribute.isEmpty()) { + return ""; + } + return attribute.stream() + .filter(str -> str != null && !str.isEmpty()) + .collect(Collectors.joining(DELIMITER)); + } catch (Exception e) { + throw new InvalidValueException(ErrorCode.INVALID_INPUT_VALUE); + } + } + + @Override + public List<String> convertToEntityAttribute(String dbData) { + if (dbData == null || dbData.isEmpty()) { + return List.of(); + } + return Arrays.stream(dbData.split(DELIMITER)) + .filter(str -> str != null && !str.isEmpty()) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java index 03b6e53..dc9c92d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java @@ -1,6 +1,5 @@ package com.moplus.moplus_server.domain.problem.service; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -13,8 +12,8 @@ public class ProblemDeleteService { private final ProblemRepository problemRepository; @Transactional - public void deleteProblem(String problemId) { - problemRepository.existsByIdElseThrow(new ProblemAdminId(problemId)); - problemRepository.deleteById(new ProblemAdminId(problemId)); + public void deleteProblem(Long id) { + problemRepository.existsByIdElseThrow(id); + problemRepository.deleteById(id); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java index 6abd479..a3b0fa1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java @@ -1,7 +1,6 @@ package com.moplus.moplus_server.domain.problem.service; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import lombok.RequiredArgsConstructor; @@ -15,8 +14,8 @@ public class ProblemGetService { private final ProblemRepository problemRepository; @Transactional(readOnly = true) - public ProblemGetResponse getProblem(String problemId) { - Problem problem = problemRepository.findByIdElseThrow(new ProblemAdminId(problemId)); + public ProblemGetResponse getProblem(Long problemId) { + Problem problem = problemRepository.findByIdElseThrow(problemId); return ProblemGetResponse.of(problem); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java index b661d89..a6f6679 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java @@ -3,7 +3,7 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemIdService; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminIdService; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; @@ -18,17 +18,17 @@ public class ProblemSaveService { private final ProblemRepository problemRepository; private final PracticeTestTagRepository practiceTestRepository; - private final ProblemIdService problemIdService; + private final ProblemAdminIdService problemAdminIdService; private final ProblemMapper problemMapper; @Transactional - public ProblemAdminId createProblem(ProblemPostRequest request) { + public Long createProblem(ProblemPostRequest request) { PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); - - ProblemAdminId problemAdminId = problemIdService.nextId(request.number(), practiceTestTag, + ProblemAdminId problemAdminId = problemAdminIdService.nextId(request.number(), practiceTestTag, request.problemType()); + Problem problem = problemMapper.from(request, problemAdminId, practiceTestTag); - return problemRepository.save(problem).getProblemAdminId(); + return problemRepository.save(problem).getId(); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java index 90a5e62..81f1086 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java @@ -5,6 +5,7 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminIdService; import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; @@ -23,6 +24,7 @@ public class ProblemUpdateService { private final ProblemRepository problemRepository; + private final ProblemAdminIdService problemAdminIdService; private final PracticeTestTagRepository practiceTestRepository; private final ConceptTagRepository conceptTagRepository; private final ChildProblemRepository childProblemRepository; @@ -30,11 +32,15 @@ public class ProblemUpdateService { private final ProblemMapper problemMapper; @Transactional - public ProblemGetResponse updateProblem(String problemId, ProblemUpdateRequest request) { + public ProblemGetResponse updateProblem(Long problemId, ProblemUpdateRequest request) { + PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); - Problem problem = problemRepository.findByIdElseThrow(new ProblemAdminId(problemId)); - PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(problem.getPracticeTestId()); - Problem inputProblem = problemMapper.from(request, problem.getProblemAdminId(), practiceTestTag); + Problem problem = problemRepository.findByIdElseThrow(problemId); + + ProblemAdminId problemAdminId = problemAdminIdService.nextId(request.number(), practiceTestTag, + request.problemType()); + + Problem inputProblem = problemMapper.from(request, problemAdminId, practiceTestTag); problem.update(inputProblem); problem.deleteChildProblem(request.deleteChildProblems()); diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index 268b392..43d09db 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -1,7 +1,6 @@ package com.moplus.moplus_server.domain.problemset.domain; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.global.common.BaseEntity; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; @@ -43,20 +42,20 @@ public class ProblemSet extends BaseEntity { @ElementCollection @CollectionTable(name = "problem_set_problems", joinColumns = @JoinColumn(name = "problem_set_id")) - @Column(name = "problem_admin_id") + @Column(name = "problem_id") @OrderColumn(name = "sequence") - private List<ProblemAdminId> problemAdminIds = new ArrayList<>(); + private List<Long> problemIds = new ArrayList<>(); @Builder - public ProblemSet(String title, List<ProblemAdminId> problemAdminIds) { + public ProblemSet(String title, List<Long> problemIds) { this.title = new Title(title); this.isDeleted = false; this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED; - this.problemAdminIds = problemAdminIds; + this.problemIds = problemIds; } - public void updateProblemOrder(List<ProblemAdminId> newProblems) { - this.problemAdminIds = new ArrayList<>(newProblems); + public void updateProblemOrder(List<Long> newProblems) { + this.problemIds = new ArrayList<>(newProblems); } public void deleteProblemSet() { @@ -78,8 +77,8 @@ public void toggleConfirm(List<Problem> problems) { this.confirmStatus = this.confirmStatus.toggle(); } - public void updateProblemSet(String title, List<ProblemAdminId> newProblems) { + public void updateProblemSet(String title, List<Long> newProblems) { this.title = new Title(title); - this.problemAdminIds = newProblems; + this.problemIds = newProblems; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java index 602b249..e398789 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java @@ -3,6 +3,6 @@ import java.util.List; public record ProblemReorderRequest( - List<String> newProblems + List<Long> newProblems ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java index 0de61e7..d8d1abf 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java @@ -1,17 +1,16 @@ package com.moplus.moplus_server.domain.problemset.dto.request; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import java.util.List; public record ProblemSetPostRequest( String problemSetTitle, - List<String> problems + List<Long> problems ) { - public ProblemSet toEntity(List<ProblemAdminId> problemAdminIdList) { + public ProblemSet toEntity(List<Long> problemIdList) { return ProblemSet.builder() .title(this.problemSetTitle) - .problemAdminIds(problemAdminIdList) + .problemIds(problemIdList) .build(); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java index 6752f0e..3cd3091 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java @@ -4,6 +4,6 @@ public record ProblemSetUpdateRequest( String problemSetTitle, - List<String> problems + List<Long> problemIds ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java index 024d9ab..78ce956 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -16,7 +16,7 @@ public record ProblemSummaryResponse( public static ProblemSummaryResponse of(Problem problem, String practiceTestName, List<String> tagNames) { return ProblemSummaryResponse.builder() - .problemId(problem.getId().toString()) + .problemId(problem.getProblemAdminId().getId()) .number(problem.getNumber()) .memo(problem.getMemo()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index 8cccfba..7fc901c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -26,7 +26,7 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p List<String> conceptTagNames) { return queryFactory .from(problemSet) - .leftJoin(problem).on(problem.problemAdminId.in(problemSet.problemAdminIds)) // 문제 세트 내 포함된 문항과 조인 + .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( @@ -53,7 +53,7 @@ public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, S List<String> conceptTagNames) { return queryFactory .from(problemSet) - .leftJoin(problem).on(problem.problemAdminId.in(problemSet.problemAdminIds)) // 문제 세트 내 포함된 문항과 조인 + .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java index f236958..16e1d2d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java @@ -4,7 +4,6 @@ import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; @@ -39,8 +38,8 @@ public ProblemSetGetResponse getProblemSet(Long problemSetId) { .orElse(null); List<ProblemSummaryResponse> problemSummaries = new ArrayList<>(); - for (ProblemAdminId problemAdminId : problemSet.getProblemAdminIds()) { - Problem problem = problemRepository.findByIdElseThrow(problemAdminId); + for (Long problemId : problemSet.getProblemIds()) { + Problem problem = problemRepository.findByIdElseThrow(problemId); PracticeTestTag practiceTestTag = practiceTestTagRepository.findByIdElseThrow(problem.getPracticeTestId()); List<String> tagNames = conceptTagRepository.findAllByIdsElseThrow(problem.getConceptTagIds()) .stream() diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java index 57627ae..6df9a8f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -1,13 +1,11 @@ package com.moplus.moplus_server.domain.problemset.service; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,16 +24,11 @@ public Long createProblemSet(ProblemSetPostRequest request) { throw new InvalidValueException(ErrorCode.EMPTY_PROBLEMS_ERROR); } - // 문제 ID 리스트를 ProblemId 객체로 변환 - List<ProblemAdminId> problemAdminIdList = request.problems().stream() - .map(ProblemAdminId::new) - .toList(); - // 모든 문항이 DB에 존재하는지 검증 - problemAdminIdList.forEach(problemRepository::findByIdElseThrow); + request.problems().forEach(problemRepository::findByIdElseThrow); // ProblemSet 생성 - ProblemSet problemSet = request.toEntity(problemAdminIdList); + ProblemSet problemSet = request.toEntity(request.problems()); return problemSetRepository.save(problemSet).getId(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java index 3bb8fc9..e588c82 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java @@ -1,7 +1,6 @@ package com.moplus.moplus_server.domain.problemset.service; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; @@ -12,7 +11,6 @@ import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,12 +26,7 @@ public class ProblemSetUpdateService { public void reorderProblems(Long problemSetId, ProblemReorderRequest request) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); - // 기존 문항 ID 리스트 업데이트 (순서 반영) - List<ProblemAdminId> updatedProblemAdminIds = request.newProblems().stream() - .map(ProblemAdminId::new) - .collect(Collectors.toList()); - - problemSet.updateProblemOrder(updatedProblemAdminIds); + problemSet.updateProblemOrder(request.newProblems()); } @Transactional @@ -41,25 +34,21 @@ public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); // 빈 문항 유효성 검증 - if (request.problems().isEmpty()) { + if (request.problemIds().isEmpty()) { throw new InvalidValueException(ErrorCode.EMPTY_PROBLEMS_ERROR); } - // 문항 리스트 검증 - List<ProblemAdminId> problemAdminIdList = request.problems().stream() - .map(ProblemAdminId::new) - .collect(Collectors.toList()); - problemAdminIdList.forEach(problemRepository::findByIdElseThrow); + request.problemIds().forEach(problemRepository::findByIdElseThrow); - problemSet.updateProblemSet(request.problemSetTitle(), problemAdminIdList); + problemSet.updateProblemSet(request.problemSetTitle(), request.problemIds()); } @Transactional public ProblemSetConfirmStatus toggleConfirmProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); List<Problem> problems = new ArrayList<>(); - for (ProblemAdminId problemAdminId : problemSet.getProblemAdminIds()) { - Problem problem = problemRepository.findByIdElseThrow(problemAdminId); + for (Long problemId : problemSet.getProblemIds()) { + Problem problem = problemRepository.findByIdElseThrow(problemId); problems.add(problem); } problemSet.toggleConfirm(problems); diff --git a/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java b/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java index de5fd60..9e163aa 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java +++ b/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java @@ -3,10 +3,13 @@ import com.moplus.moplus_server.global.error.exception.BusinessException; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; +import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.transaction.CannotCreateTransactionException; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -25,6 +28,18 @@ protected ResponseEntity<ErrorResponse> handleBusinessException(final BusinessEx return new ResponseEntity<>(response, errorCode.getStatus()); } + @ExceptionHandler(MethodArgumentNotValidException.class) + protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) { + final List<String> errors = ex.getBindingResult() + .getAllErrors() + .stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .toList(); + + ErrorResponse response = ErrorResponse.from(ErrorCode.INVALID_INPUT_VALUE); + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } + @ExceptionHandler(NoHandlerFoundException.class) public ResponseEntity<String> handleNoHandlerFoundException(NoHandlerFoundException ex) { return new ResponseEntity<>("존재하지 않은 요청 엔드포인트입니다", HttpStatus.BAD_REQUEST); diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 6b645b8..25f5519 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -33,6 +33,7 @@ public enum ErrorCode { INVALID_MULTIPLE_CHOICE_ANSWER(HttpStatus.BAD_REQUEST, "객관식 문제의 정답은 1~5 사이의 숫자여야 합니다"), INVALID_SHORT_NUMBER_ANSWER(HttpStatus.BAD_REQUEST, "주관식 문제의 정답은 0~999 사이의 숫자여야 합니다"), INVALID_CONFIRM_PROBLEM(HttpStatus.BAD_REQUEST, "유효하지 않은 문항들 : "), + INVALID_DIFFICULTY(HttpStatus.BAD_REQUEST, "난이도는 1~10 사이의 숫자여야 합니다"), //새끼 문항 CHILD_PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 새끼 문제를 찾을 수 없습니다"), diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java index 8b78a53..184e71c 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java @@ -21,11 +21,12 @@ @ExtendWith(MockitoExtension.class) class ProblemAdminIdServiceTest { + public static final String ID_LENGTH = "10"; @Mock private ProblemRepository problemRepository; @InjectMocks - private ProblemIdService problemIdService; + private ProblemAdminIdService problemAdminIdService; private PracticeTestTag practiceTestTag; @@ -41,18 +42,19 @@ void setUp() { void nextId_정상생성_및_중복확인() { // given int 문제번호 = 20; - when(problemRepository.existsById(any(ProblemAdminId.class))).thenReturn(false); // 중복 없음 + ProblemType problemType = ProblemType.GICHUL_PROBLEM; + when(problemRepository.existsByProblemAdminId(any())).thenReturn(false); // 중복 없음 // when - ProblemAdminId generatedId = problemIdService.nextId(문제번호, practiceTestTag); + ProblemAdminId generatedId = problemAdminIdService.nextId(문제번호, practiceTestTag, problemType); // then assertThat(generatedId).isNotNull(); - assertThat(generatedId.getId()).matches("\\d{13}"); // ID 형식이 맞는지 확인 - assertThat(generatedId.getId()).startsWith("2405200120"); + assertThat(generatedId.getId()).matches("\\d{" + ID_LENGTH + "}"); // ID 형식이 맞는지 확인 + assertThat(generatedId.getId()).startsWith("12240520"); // 문제 ID 중복 확인을 위해 existsById 호출 확인 - verify(problemRepository, atLeastOnce()).existsById(any(ProblemAdminId.class)); + verify(problemRepository, atLeastOnce()).existsByProblemAdminId(any()); } @@ -60,19 +62,20 @@ void setUp() { void nextId_중복발생시_다시_생성() { // given int 문제번호 = 2; - when(problemRepository.existsById(any(ProblemAdminId.class))) + ProblemType problemType = ProblemType.GICHUL_PROBLEM; + when(problemRepository.existsByProblemAdminId(any())) .thenReturn(true) // 첫 번째 생성된 ID는 중복됨 .thenReturn(false); // 두 번째는 중복 없음 // when - ProblemAdminId generatedId = problemIdService.nextId(문제번호, practiceTestTag); + ProblemAdminId generatedId = problemAdminIdService.nextId(문제번호, practiceTestTag, problemType); // then assertThat(generatedId).isNotNull(); - assertThat(generatedId.getId()).matches("\\d{13}"); - assertThat(generatedId.getId()).startsWith("2405020120"); + assertThat(generatedId.getId()).matches("\\d{" + ID_LENGTH + "}"); + assertThat(generatedId.getId()).startsWith("12240502"); // 중복된 ID가 나왔으므로 existsById가 최소 두 번 이상 호출되었는지 확인 - verify(problemRepository, atLeast(2)).existsById(any(ProblemAdminId.class)); + verify(problemRepository, atLeast(2)).existsByProblemAdminId(any()); } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java index 712115d..4eb1e25 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java @@ -23,12 +23,12 @@ public class ProblemSearchRepositoryCustomTest { @Test void problemId_일부_포함_검색() { // when - List<ProblemSearchGetResponse> result = problemSearchRepository.search("240520012", null, null); + List<ProblemSearchGetResponse> result = problemSearchRepository.search("12240520", null, null); // then assertThat(result).hasSize(2); assertThat(result).extracting(ProblemSearchGetResponse::getProblemId) - .containsExactlyInAnyOrder("240520012001", "240520012002"); + .containsExactlyInAnyOrder("1224052001", "1224052002"); } @Test @@ -38,7 +38,7 @@ public class ProblemSearchRepositoryCustomTest { // then assertThat(result).hasSize(1); - assertThat(result.get(0).getProblemId()).isEqualTo("240520012001"); + assertThat(result.get(0).getProblemId()).isEqualTo("1224052001"); } @Test @@ -49,17 +49,17 @@ public class ProblemSearchRepositoryCustomTest { // then assertThat(result).hasSize(2); assertThat(result).extracting(ProblemSearchGetResponse::getProblemId) - .containsExactlyInAnyOrder("240520012001", "240520012002"); + .containsExactlyInAnyOrder("1224052001", "1224052002"); } @Test void problemId_이름_conceptTagIds_모두_적용된_검색() { // when - List<ProblemSearchGetResponse> result = problemSearchRepository.search("2405200120", "설명 1", List.of(1L)); + List<ProblemSearchGetResponse> result = problemSearchRepository.search("12240520", "설명 1", List.of(1L)); // then assertThat(result).hasSize(1); - assertThat(result.get(0).getProblemId()).isEqualTo("240520012001"); + assertThat(result.get(0).getProblemId()).isEqualTo("1224052001"); } @Test diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java index f19a71c..21b2a2f 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java @@ -1,18 +1,15 @@ package com.moplus.moplus_server.domain.problem.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; -import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; -import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemPostRequest; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; -import java.util.List; -import java.util.Set; -import java.util.stream.IntStream; +import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -32,117 +29,46 @@ class ProblemSaveServiceTest { @Autowired private ProblemRepository problemRepository; - private ProblemPostRequest problemPostRequestOutOfOrder; - private ProblemPostRequest problemPostRequestInOrder; + private ProblemPostRequest problemPostRequest; @BeforeEach void setUp() { - // 🔹 1. 일부러 순서를 뒤죽박죽으로 설정한 문제 - ChildProblemPostRequest childProblem1 = new ChildProblemPostRequest( - "child1.png", AnswerType.SHORT_STRING_ANSWER, "정답1", Set.of(3L, 4L), 3 - ); - ChildProblemPostRequest childProblem2 = new ChildProblemPostRequest( - "child2.png", AnswerType.MULTIPLE_CHOICE, "1", Set.of(5L, 6L), 1 - ); - ChildProblemPostRequest childProblem3 = new ChildProblemPostRequest( - "child3.png", AnswerType.MULTIPLE_CHOICE, "2", Set.of(3L, 4L), 0 - ); - ChildProblemPostRequest childProblem4 = new ChildProblemPostRequest( - "child4.png", AnswerType.SHORT_NUMBER_ANSWER, "0", Set.of(1L, 2L), 2 - ); - - problemPostRequestOutOfOrder = new ProblemPostRequest( - Set.of(1L, 2L), - 1L, - 21, - "1", - "설명", - "mainProblem.png", - "mainAnalysis.png", - "readingTip.png", - "seniorTip.png", - "prescription.png", - List.of(childProblem1, childProblem2, childProblem3, childProblem4) // 🔹 순서 뒤죽박죽 - ); - - // 🔹 2. 순서가 올바른 상태에서 입력되는 문제 - problemPostRequestInOrder = new ProblemPostRequest( - Set.of(1L, 2L), + problemPostRequest = new ProblemPostRequest( + ProblemType.GICHUL_PROBLEM, 1L, - 20, - "2", - "다른 설명", - "mainProblem2.png", - "mainAnalysis2.png", - "readingTip2.png", - "seniorTip2.png", - "prescription2.png", - List.of(childProblem3, childProblem2, childProblem4, childProblem1) // 🔹 순서 유지 (0,1,2,3) + 20 ); - } - @Test - void 정상동작() { - - // when - ProblemAdminId createdProblemAdminId = problemSaveService.createProblem(problemPostRequestInOrder); - - // then - assertThat(createdProblemAdminId).isNotNull(); - assertThat(createdProblemAdminId.getId()).startsWith("2405200120"); // ID 앞부분 확인 - - Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemAdminId); - - // 모든 자식 문제의 conceptTagIds가 부모 문제의 conceptTagIds에 포함되는지 검증 - Set<Long> problemTags = savedProblem.getConceptTagIds(); - problemPostRequestInOrder.childProblems().forEach(child -> { - assertThat(problemTags).containsAll(child.conceptTagIds()); - }); - - // 자식 문제의 순서 검증 - List<ChildProblem> childProblems = savedProblem.getChildProblems(); - - assertThat(childProblems).hasSize(4); // 자식 문제 개수 검증 - - // 저장된 자식 문제가 원래 요청한 `sequence` 순서와 같은지 확인 - IntStream.range(0, childProblems.size()).forEach(i -> { - assertThat(childProblems.get(i).getSequence()).isEqualTo(i); - }); + @Nested + class 문항생성 { + + @Test + void 성공() { + // when + Long createdProblemId = problemSaveService.createProblem(problemPostRequest); + + // then + assertThat(createdProblemId).isNotNull(); + + Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemId); + assertThat(savedProblem).isNotNull(); + assertThat(savedProblem.getProblemType()).isEqualTo(ProblemType.GICHUL_PROBLEM); + } + + @Test + void 문제_저장_실패_잘못된_부모_문제_ID() { + // given + ProblemPostRequest invalidParentRequest = new ProblemPostRequest( + ProblemType.GICHUL_PROBLEM, + 999L, // 존재하지 않는 parentId 사용 + 10 + ); + + // when & then + assertThatThrownBy(() -> problemSaveService.createProblem(invalidParentRequest)) + .isInstanceOf(NotFoundException.class); + } } - @Test - void 자식문제_올바른_순서_저장() { - // when - ProblemAdminId createdProblemAdminId = problemSaveService.createProblem(problemPostRequestOutOfOrder); - - // then - assertThat(createdProblemAdminId).isNotNull(); - assertThat(createdProblemAdminId.getId()).startsWith("2405210120"); // ID 앞부분 확인 - - // 저장된 문제 조회 - Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemAdminId); - - // ✅ 모든 자식 문제의 conceptTagIds가 부모 문제의 conceptTagIds에 포함되는지 검증 - Set<Long> problemTags = savedProblem.getConceptTagIds(); - problemPostRequestOutOfOrder.childProblems().forEach(child -> { - assertThat(problemTags).containsAll(child.conceptTagIds()); - }); - - // ✅ 자식 문제의 순서 검증 - List<ChildProblem> childProblems = savedProblem.getChildProblems(); - - assertThat(childProblems).hasSize(4); // 자식 문제 개수 검증 - - // 🔹 저장된 자식 문제들이 `sequence` 오름차순으로 정렬되었는지 확인 - IntStream.range(0, childProblems.size()).forEach(i -> { - assertThat(childProblems.get(i).getSequence()).isEqualTo(i); - }); - - // 🔹 정렬 후 올바른 문제인지 검증 - assertThat(childProblems.get(0).getImageUrl()).isEqualTo("child3.png"); // sequence 0 - assertThat(childProblems.get(1).getImageUrl()).isEqualTo("child2.png"); // sequence 1 - assertThat(childProblems.get(2).getImageUrl()).isEqualTo("child4.png"); // sequence 2 - assertThat(childProblems.get(3).getImageUrl()).isEqualTo("child1.png"); // sequence 3 - } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java index 1c9252a..6760214 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java @@ -1,19 +1,23 @@ package com.moplus.moplus_server.domain.problem.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.util.List; import java.util.Set; import java.util.stream.IntStream; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -40,7 +44,7 @@ class ProblemUpdateServiceTest { void setUp() { problemAdminId = new ProblemAdminId("240520012001"); - // 🔹 새 자식 문제 추가 + // 새 자식 문제 추가 ChildProblemUpdateRequest newChildProblem = new ChildProblemUpdateRequest( null, "newChild.png", @@ -50,7 +54,7 @@ void setUp() { 1 ); - // 🔹 기존 자식 문제 업데이트 + // 기존 자식 문제 업데이트 ChildProblemUpdateRequest updateChildProblem = new ChildProblemUpdateRequest( 1L, // 기존 자식 문제 ID "updatedChild.png", @@ -60,53 +64,139 @@ void setUp() { 0 ); - // 🔹 기존 자식 문제 삭제 + // 기존 자식 문제 삭제 List<Long> deleteChildProblem = List.of(2L); // 삭제할 자식 문제 ID problemUpdateRequest = new ProblemUpdateRequest( - Set.of(1L, 2L, 3L), // 업데이트할 부모 문제의 Concept Tags - 1, // 문제 정답 - "수정된 설명", // 새로운 설명 + ProblemType.VARIANT_PROBLEM, + 2L, + 10, + Set.of(1L, 2L, 3L), + "정답", + "업데이트된 제목", + 3, + "업데이트된 메모", "updatedMainProblem.png", "updatedMainAnalysis.png", + "updatedMainHandwriting.png", // 추가 "updatedReadingTip.png", "updatedSeniorTip.png", - "updatedPrescription.png", - List.of(newChildProblem, updateChildProblem), // 업데이트할 자식 문제 - deleteChildProblem // 삭제할 자식 문제 + List.of("prescription1.png", "prescription2.png"), // List<String>으로 변경 + AnswerType.SHORT_STRING_ANSWER, + List.of(newChildProblem, updateChildProblem), + deleteChildProblem ); } - @Test - void 문제_업데이트_정상동작() { - // when - ProblemGetResponse response = problemUpdateService.updateProblem(problemAdminId.getId(), - problemUpdateRequest); - - // then - assertThat(response).isNotNull(); - assertThat(response.comment()).isEqualTo("수정된 설명"); // ✅ 설명이 변경되었는지 검증 - assertThat(response.mainProblemImageUrl()).isEqualTo("updatedMainProblem.png"); // ✅ 이미지 URL 변경 확인 - - Problem updatedProblem = problemRepository.findByIdElseThrow(problemAdminId); - - // ✅ 자식 문제 개수 검증 - List<ChildProblem> childProblems = updatedProblem.getChildProblems(); - assertThat(childProblems).hasSize(2); // 기존 2개 → 1개 삭제, 1개 추가 후 2개 - - // ✅ 부모 문제의 conceptTagIds가 자식 문제의 conceptTagIds를 모두 포함하는지 검증 - Set<Long> problemTags = updatedProblem.getConceptTagIds(); - updatedProblem.getChildProblems().forEach(child -> { - assertThat(problemTags).containsAll(child.getConceptTagIds()); - }); - - // ✅ 자식 문제 순서가 올바르게 정렬되었는지 확인 - IntStream.range(0, childProblems.size()).forEach(i -> { - assertThat(childProblems.get(i).getSequence()).isEqualTo(i); - }); - - // ✅ 개별 자식 문제 검증 - assertThat(childProblems.get(0).getImageUrl()).isEqualTo("updatedChild.png"); // 기존 자식 문제 업데이트 확인 - assertThat(childProblems.get(1).getImageUrl()).isEqualTo("newChild.png"); // 새 자식 문제 추가 확인 + @Nested + class 문제_업데이트_정상_동작 { + + @Test + void 문제_업데이트_성공() { + // when + ProblemGetResponse response = problemUpdateService.updateProblem(1L, + problemUpdateRequest); + + // then + assertThat(response).isNotNull(); + assertThat(response.problemId()).startsWith("22230310"); // 문제 ID 확인 + assertThat(response.problemType()).isEqualTo(ProblemType.VARIANT_PROBLEM); + assertThat(response.practiceTestId()).isEqualTo(2L); + assertThat(response.number()).isEqualTo(10); + assertThat(response.conceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(1L, 2L, 3L)); + assertThat(response.answer()).isEqualTo("정답"); + assertThat(response.title()).isEqualTo("업데이트된 제목"); + assertThat(response.difficulty()).isEqualTo(3); + assertThat(response.memo()).isEqualTo("업데이트된 메모"); + + // 이미지 URL 검증 + assertThat(response.mainProblemImageUrl()).isEqualTo("updatedMainProblem.png"); + assertThat(response.mainAnalysisImageUrl()).isEqualTo("updatedMainAnalysis.png"); + assertThat(response.readingTipImageUrl()).isEqualTo("updatedReadingTip.png"); + assertThat(response.seniorTipImageUrl()).isEqualTo("updatedSeniorTip.png"); + assertThat(response.mainHandwritingExplanationImageUrl()) + .isEqualTo("updatedMainHandwriting.png"); + assertThat(response.prescriptionImageUrls()) + .containsExactly("prescription1.png", "prescription2.png"); + + // 답안 유형 검증 + assertThat(response.answerType()).isEqualTo(AnswerType.SHORT_STRING_ANSWER); + + Problem updatedProblem = problemRepository.findByIdElseThrow(1L); + + // 자식 문제 검증 + List<ChildProblem> childProblems = updatedProblem.getChildProblems(); + assertThat(childProblems).hasSize(2); // 기존 2개 → 1개 삭제, 1개 추가 후 2개 + + // 첫 번째 자식 문제 검증 (업데이트된 기존 문제) + ChildProblem updatedChild = childProblems.get(0); + assertThat(updatedChild.getId()).isEqualTo(1L); + assertThat(updatedChild.getImageUrl()).isEqualTo("updatedChild.png"); + assertThat(updatedChild.getAnswerType()).isEqualTo(AnswerType.MULTIPLE_CHOICE); + assertThat(updatedChild.getAnswer()).isEqualTo("2"); + assertThat(updatedChild.getConceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(2L, 3L)); + assertThat(updatedChild.getSequence()).isEqualTo(0); + + // 두 번째 자식 문제 검증 (새로 추가된 문제) + ChildProblem newChild = childProblems.get(1); + assertThat(newChild.getImageUrl()).isEqualTo("newChild.png"); + assertThat(newChild.getAnswerType()).isEqualTo(AnswerType.SHORT_STRING_ANSWER); + assertThat(newChild.getAnswer()).isEqualTo("새로운 정답"); + assertThat(newChild.getConceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(1L, 2L)); + assertThat(newChild.getSequence()).isEqualTo(1); + + // 부모 문제의 conceptTagIds가 자식 문제의 conceptTagIds를 모두 포함하는지 검증 + Set<Long> problemTags = updatedProblem.getConceptTagIds(); + childProblems.forEach(child -> { + assertThat(problemTags).containsAll(child.getConceptTagIds()); + }); + + // 자식 문제 순서가 올바르게 정렬되었는지 확인 + IntStream.range(0, childProblems.size()).forEach(i -> { + assertThat(childProblems.get(i).getSequence()).isEqualTo(i); + }); + } + } + + @Nested + class 문제_업데이트_예외_처리 { + + @Test + void 문제_업데이트_실패_존재하지_않는_ID() { + // given + String invalidId = "999999999999"; // 존재하지 않는 문제 ID + + // when & then + assertThatThrownBy(() -> problemUpdateService.updateProblem(9999L, problemUpdateRequest)) + .isInstanceOf(NotFoundException.class); + } + + @Test + void 문제_업데이트_실패_잘못된_ConceptTag() { + // given + ProblemUpdateRequest invalidRequest = new ProblemUpdateRequest( + ProblemType.GICHUL_PROBLEM, + 1L, + 20, + Set.of(999L, 1000L), + "정답", + "잘못된 제목", + 3, + "잘못된 메모", + "updatedMainProblem.png", + "updatedMainAnalysis.png", + "updatedMainHandwriting.png", // 추가 + "updatedReadingTip.png", + "updatedSeniorTip.png", + List.of("prescription1.png"), // List<String>으로 변경 + AnswerType.SHORT_STRING_ANSWER, + List.of(), + List.of() + ); + + // when & then + assertThatThrownBy(() -> problemUpdateService.updateProblem(9999L, invalidRequest)) + .isInstanceOf(NotFoundException.class); + } } -} \ No newline at end of file +} diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index 1beeee6..539d0a0 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -44,7 +44,7 @@ void setUp() { // 초기 문항 세트 생성 요청 데이터 준비 problemSetPostRequest = new ProblemSetPostRequest( "초기 문항세트", - List.of("24052001001", "24052001002", "24052001003") + List.of(1L, 2L, 3L) ); } @@ -59,10 +59,7 @@ void setUp() { assertThat(savedProblemSet).isNotNull(); assertThat(savedProblemSet.getTitle().getValue()).isEqualTo("초기 문항세트"); - assertThat(savedProblemSet.getProblemAdminIds()).hasSize(3); - assertThat(savedProblemSet.getProblemAdminIds().get(0).getId()).isEqualTo("24052001001"); - assertThat(savedProblemSet.getProblemAdminIds().get(1).getId()).isEqualTo("24052001002"); - assertThat(savedProblemSet.getProblemAdminIds().get(2).getId()).isEqualTo("24052001003"); + assertThat(savedProblemSet.getProblemIds()).hasSize(3); } @Test @@ -72,7 +69,7 @@ void setUp() { // when ProblemReorderRequest reorderRequest = new ProblemReorderRequest( - List.of("24052001003", "24052001001", "24052001002") + List.of(1L, 2L, 3L) ); problemSetUpdateService.reorderProblems(problemSetId, reorderRequest); @@ -80,9 +77,6 @@ void setUp() { ProblemSet updatedProblemSet = problemSetRepository.findById(problemSetId) .orElseThrow(() -> new IllegalArgumentException("문항세트를 찾을 수 없습니다.")); - assertThat(updatedProblemSet.getProblemAdminIds().get(0).getId()).isEqualTo("24052001003"); - assertThat(updatedProblemSet.getProblemAdminIds().get(1).getId()).isEqualTo("24052001001"); - assertThat(updatedProblemSet.getProblemAdminIds().get(2).getId()).isEqualTo("24052001002"); } @Test @@ -93,7 +87,7 @@ void setUp() { // when ProblemSetUpdateRequest updateRequest = new ProblemSetUpdateRequest( "업데이트된 문항세트", - List.of("24052001002", "24052001003") + List.of(1L, 2L) ); problemSetUpdateService.updateProblemSet(problemSetId, updateRequest); @@ -101,9 +95,8 @@ void setUp() { ProblemSet updatedProblemSet = problemSetRepository.findByIdElseThrow(problemSetId); assertThat(updatedProblemSet.getTitle().getValue()).isEqualTo("업데이트된 문항세트"); - assertThat(updatedProblemSet.getProblemAdminIds()).hasSize(2); - assertThat(updatedProblemSet.getProblemAdminIds().get(0).getId()).isEqualTo("24052001002"); - assertThat(updatedProblemSet.getProblemAdminIds().get(1).getId()).isEqualTo("24052001003"); + assertThat(updatedProblemSet.getProblemIds()).hasSize(2); + } @Test @@ -130,7 +123,7 @@ void setUp() { // 유효하지 않은 문항을 포함하도록 설정 (문항 ID가 존재하지 않거나 필수 필드가 누락된 경우) ProblemSetUpdateRequest invalidUpdateRequest = new ProblemSetUpdateRequest( "유효하지 않은 문항세트", - List.of("24052001001", "24052001004") + List.of(1L, 4L) ); problemSetUpdateService.updateProblemSet(problemSetId, invalidUpdateRequest); @@ -160,12 +153,12 @@ void setUp() { // given ProblemSetPostRequest emptyTitleRequest = new ProblemSetPostRequest( "", // 빈 문자열 제목 - List.of("24052001001", "24052001002", "24052001003") + List.of(1L, 2L, 3L) ); ProblemSetPostRequest nullTitleRequest = new ProblemSetPostRequest( null, // null 제목 - List.of("24052001001", "24052001002", "24052001003") + List.of(1L, 2L, 3L) ); // when diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index 5085caf..4a34089 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -46,7 +46,7 @@ public class ProblemSetSearchRepositoryCustomTest { @Test void 문항타이틀_포함_검색() { // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, "설명 1", null); + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, "설명", null); // then assertThat(result).hasSize(2); diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java index 386779b..20dc272 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java @@ -3,9 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; -import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; @@ -13,7 +11,6 @@ import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.util.List; -import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -24,7 +21,7 @@ @Transactional @ActiveProfiles("h2test") -@Sql({"/practice-test-tag.sql", "/concept-tag.sql"}) +@Sql({"/practice-test-tag.sql", "/concept-tag.sql", "/insert-problem.sql"}) @SpringBootTest public class ProblemSetGetServiceTest { @@ -41,47 +38,13 @@ public class ProblemSetGetServiceTest { private PracticeTestTagRepository practiceTestTagRepository; private ProblemSet savedProblemSet; - private Problem savedProblem; @BeforeEach void setUp() { - // 문제 생성 요청 데이터 준비 - ProblemPostRequest problemPostRequest = new ProblemPostRequest( - Set.of(1L, 2L), - 1L, - 1, - "1", - "문제 설명", - "mainProblem.png", - "mainAnalysis.png", - "readingTip.png", - "seniorTip.png", - "prescription.png", - List.of() - ); - - // 문제 저장 - ProblemAdminId createdProblemAdminId = problemRepository.save( - new Problem( - new ProblemAdminId("24052001001"), - practiceTestTagRepository.findByIdElseThrow(1L), - 1, - "1", - "문제 설명", - "mainProblem.png", - "mainAnalysis.png", - "readingTip.png", - "seniorTip.png", - "prescription.png", - Set.of(1L, 2L) - ) - ).getId(); - - savedProblem = problemRepository.findByIdElseThrow(createdProblemAdminId); // 문항세트 저장 savedProblemSet = problemSetRepository.save( - new ProblemSet("테스트 문항세트", List.of(savedProblem.getId())) + new ProblemSet("테스트 문항세트", List.of(1L)) ); } @@ -94,7 +57,7 @@ void setUp() { assertThat(response).isNotNull(); assertThat(response.title()).isEqualTo("테스트 문항세트"); assertThat(response.problemSummaries()).hasSize(1); - assertThat(response.problemSummaries().get(0).problemId()).isEqualTo(savedProblem.getId().toString()); + assertThat(response.problemSummaries().get(0).problemId()).isEqualTo("1224052001"); assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); } @@ -109,25 +72,11 @@ void setUp() { @Test void 문항세트_조회_성공_테스트_여러개() { - // given: 여러 개의 문제를 저장 - Problem savedProblem2 = problemRepository.save( - new Problem( - new ProblemAdminId("24052001002"), - practiceTestTagRepository.findByIdElseThrow(1L), - 2, - "2", - "문제 설명2", - "mainProblem2.png", - "mainAnalysis2.png", - "readingTip2.png", - "seniorTip2.png", - "prescription2.png", - Set.of(3L, 4L) - ) - ); + // given + PracticeTestTag practiceTestTag = practiceTestTagRepository.findByIdElseThrow(1L); ProblemSet multipleProblemSet = problemSetRepository.save( - new ProblemSet("여러 문항 테스트 문항세트", List.of(savedProblem.getId(), savedProblem2.getId())) + new ProblemSet("여러 문항 테스트 문항세트", List.of(1L, 2L)) ); // when @@ -139,13 +88,13 @@ void setUp() { assertThat(response.problemSummaries()).hasSize(2); // 첫 번째 문제 검증 - assertThat(response.problemSummaries().get(0).problemId()).isEqualTo(savedProblem.getId().toString()); + assertThat(response.problemSummaries().get(0).problemId()).isEqualTo("1224052001"); assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); // 두 번째 문제 검증 - assertThat(response.problemSummaries().get(1).problemId()).isEqualTo(savedProblem2.getId().toString()); + assertThat(response.problemSummaries().get(1).problemId()).isEqualTo("1224052002"); assertThat(response.problemSummaries().get(1).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); - assertThat(response.problemSummaries().get(1).tagNames()).contains("삼각함수 개념", "행렬 개념"); + assertThat(response.problemSummaries().get(1).tagNames()).contains("미분 개념", "삼각함수 개념"); } } \ No newline at end of file diff --git a/src/test/resources/insert-problem-set.sql b/src/test/resources/insert-problem-set.sql index 051949c..ffca03e 100644 --- a/src/test/resources/insert-problem-set.sql +++ b/src/test/resources/insert-problem-set.sql @@ -1,5 +1,7 @@ -DELETE FROM problem_set_problems; -DELETE FROM problem_set; +DELETE +FROM problem_set_problems; +DELETE +FROM problem_set; -- 문제 세트 추가 INSERT INTO problem_set (problem_set_id, title, is_deleted, confirm_status) @@ -9,8 +11,8 @@ VALUES (2, '2025년 5월 고3 모의고사 문제 세트', false, 'CONFIRMED'); -- 문제 세트에 포함된 문제 추가 INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) -VALUES (1, '240520012001', 0), - (1, '240520012002', 1); +VALUES (1, 1, 0), + (1, 2, 1); INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) -VALUES (2, '240520012001', 0), - (2, '240520012002', 1); \ No newline at end of file +VALUES (2, 1, 0), + (2, 2, 1); \ No newline at end of file diff --git a/src/test/resources/insert-problem.sql b/src/test/resources/insert-problem.sql index d9ecdfb..add8767 100644 --- a/src/test/resources/insert-problem.sql +++ b/src/test/resources/insert-problem.sql @@ -1,32 +1,47 @@ -DELETE -FROM child_problem_concept; -DELETE -FROM child_problem; +INSERT INTO problem (problem_id, + problem_admin_id, + practice_test_id, + number, + problem_type, + title, + answer, + difficulty, + memo, + main_problem_image_url, + main_analysis_image_url, + reading_tip_image_url, + senior_tip_image_url, + prescription_image_urls, + answer_type, + is_confirmed) +VALUES (1, '1224052001', 1, 1, 'GICHUL_PROBLEM', '제목1', '1', 5, '기존 문제 설명 1', + 'mainProblem.png1', 'mainAnalysis.png1', 'readingTip.png1', 'seniorTip.png1', + 'prescription.png1', 'MULTIPLE_CHOICE', false), + (2, '1224052002', 1, 1, 'GICHUL_PROBLEM', '제목2', '1', 5, '기존 문제 설명 2', + 'mainProblem.png2', 'mainAnalysis.png2', 'readingTip.png2', 'seniorTip.png2', + 'prescription.png2', 'MULTIPLE_CHOICE', false); -INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, - main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, - is_published, is_variation) -VALUES ('240520012001', 1, 1, '1', '기존 문제 설명 1', - 'mainProblem.png1', 'mainAnalysis.png1', 'readingTip.png1', 'seniorTip.png1', 'prescription.png1', - false, false), - ('240520012002', 1, 1, '1', '기존 문제 설명 2', - 'mainProblem.png2', 'mainAnalysis.png2', 'readingTip.png2', 'seniorTip.png2', 'prescription.png2', - false, false); --- 기존 자식 문제(ChildProblem) 삽입 -INSERT INTO child_problem (child_problem_id, problem_id, image_url, problem_type, answer, sequence) -VALUES (1, '240520012001', 'child1.png', 'MULTIPLE_CHOICE', '1', 0), - (2, '240520012001', 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 0); +INSERT INTO child_problem (child_problem_id, + problem_id, -- Long 타입으로 변경 (부모 Problem의 id 참조) + image_url, + answer_type, + answer, + sequence) +VALUES (1, 1, 'child1.png', 'MULTIPLE_CHOICE', '1', 0), + (2, 1, 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 0); -- 문제-컨셉 태그 연결 (기존 문제의 ConceptTag) -INSERT INTO problem_concept (problem_id, concept_tag_id) -VALUES ('240520012001', 1), - ('240520012001', 2), - ('240520012001', 3), - ('240520012002', 1), - ('240520012002', 3); +INSERT INTO problem_concept (problem_id, -- Long 타입으로 변경 (Problem의 id 참조) + concept_tag_id) +VALUES (1, 1), + (1, 2), + (1, 3), + (2, 1), + (2, 3); -- 자식 문제-컨셉 태그 연결 -INSERT INTO child_problem_concept (child_problem_id, concept_tag_id) +INSERT INTO child_problem_concept (child_problem_id, + concept_tag_id) VALUES (1, 3), (1, 4), (2, 5), diff --git a/src/test/resources/insert-problem2.sql b/src/test/resources/insert-problem2.sql index 6e50eca..b999009 100644 --- a/src/test/resources/insert-problem2.sql +++ b/src/test/resources/insert-problem2.sql @@ -1,30 +1,83 @@ -DELETE FROM problem_concept; -DELETE -FROM child_problem_concept; -DELETE -FROM child_problem; -DELETE FROM problem_set_problems; -DELETE FROM problem_set; -DELETE FROM problem; - -- problem 데이터 삽입 -INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, - main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, - is_published, is_variation) -VALUES ('24052001001', 1, 1, '1', '기존 문제 설명', - 'mainProblem.png', 'mainAnalysis.png', 'readingTip.png', 'seniorTip.png', 'prescription.png', - false, false), - ('24052001002', 1, 2, '2', '문제 2 설명', - 'mainProblem2.png', 'mainAnalysis2.png', 'readingTip2.png', 'seniorTip2.png', 'prescription2.png', - false, false), - ('24052001003', 1, 3, '3', '문제 3 설명', - 'mainProblem3.png', 'mainAnalysis3.png', 'readingTip3.png', 'seniorTip3.png', 'prescription3.png', - false, false); +INSERT INTO problem (problem_id, + problem_admin_id, + practice_test_id, + number, + problem_type, + title, + answer, + difficulty, + memo, + main_problem_image_url, + main_analysis_image_url, + main_handwriting_explanation_image_url, + reading_tip_image_url, + senior_tip_image_url, + prescription_image_urls, + answer_type, + is_confirmed) +VALUES (1, '24052001001', 1, 1, 'GICHUL_PROBLEM', '제목1', '1', 5, '기존 문제 설명', + 'mainProblem.png', 'mainAnalysis.png', 'mainHandwriting1.png', 'readingTip.png', 'seniorTip.png', + 'prescription1.png, prescription2.png', 'MULTIPLE_CHOICE', false), + + (2, '24052001002', 1, 2, 'GICHUL_PROBLEM', '제목2', '2', 4, '문제 2 설명', + 'mainProblem2.png', 'mainAnalysis2.png', 'mainHandwriting2.png', 'readingTip2.png', 'seniorTip2.png', + 'prescription3.png, prescription4.png', 'MULTIPLE_CHOICE', false), + + (3, '24052001003', 1, 3, 'GICHUL_PROBLEM', '제목3', '3', 3, '문제 3 설명', + 'mainProblem3.png', 'mainAnalysis3.png', 'mainHandwriting3.png', 'readingTip3.png', 'seniorTip3.png', + 'prescription5.png, prescription6.png', 'SHORT_STRING_ANSWER', true); + +-- 자식 문제 데이터 삽입 +INSERT INTO child_problem (child_problem_id, + problem_id, + image_url, + answer_type, + answer, + sequence) +VALUES (1, 1, 'child1.png', 'MULTIPLE_CHOICE', '1', 0), + (2, 1, 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 1), + (3, 2, 'child3.png', 'MULTIPLE_CHOICE', '2', 0), + (4, 3, 'child4.png', 'SHORT_STRING_ANSWER', '3', 0); + +-- 문제-컨셉 태그 연결 +INSERT INTO problem_concept (problem_id, concept_tag_id) +VALUES (1, 1), + (1, 2), + (2, 2), + (2, 3), + (3, 3), + (3, 4); + +-- 자식 문제-컨셉 태그 연결 +INSERT INTO child_problem_concept (child_problem_id, concept_tag_id) +VALUES (1, 1), + (1, 2), + (2, 2), + (2, 3), + (3, 3), + (3, 4), + (4, 1), + (4, 4); --- 유효하지 않은 문제 데이터 삽입 (answer와 mainProblemImageUrl이 NULL) -INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, - main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, - is_published, is_variation) -VALUES ('24052001004', 1, 4, '', '유효하지 않은 문제 설명', - '', 'mainAnalysis4.png', 'readingTip4.png', 'seniorTip4.png', 'prescription4.png', - false, false); \ No newline at end of file +-- 유효하지 않은 문제 데이터 삽입 +INSERT INTO problem (problem_id, + problem_admin_id, + practice_test_id, + number, + problem_type, + title, + answer, + difficulty, + memo, + main_problem_image_url, + main_analysis_image_url, + main_handwriting_explanation_image_url, + reading_tip_image_url, + senior_tip_image_url, + prescription_image_urls, + answer_type, + is_confirmed) +VALUES (4, '24052001004', 1, 4, 'GICHUL_PROBLEM', '', '', 1, '유효하지 않은 문제 설명', + '', 'mainAnalysis4.png', '', 'readingTip4.png', 'seniorTip4.png', + '', 'MULTIPLE_CHOICE', false); \ No newline at end of file diff --git a/src/test/resources/practice-test-tag.sql b/src/test/resources/practice-test-tag.sql index 58625ce..b1f5b17 100644 --- a/src/test/resources/practice-test-tag.sql +++ b/src/test/resources/practice-test-tag.sql @@ -1,2 +1,3 @@ INSERT INTO practice_test_tag (practice_test_tag_id, name, test_year, test_month, subject, area) -VALUES (1, '2025년 5월 고2 모의고사', 2024, 5, '고2', '수학'); \ No newline at end of file +VALUES (1, '2025년 5월 고2 모의고사', 2024, 5, '고2', '수학'), + (2, '2023년 3월 고2 모의고사', 2023, 3, '고2', '수학'); \ No newline at end of file From 3f93b5e17632d0b8609166a7793e0dd7d18b6b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:05:09 +0900 Subject: [PATCH 077/115] =?UTF-8?q?[refactor/#48]=20jwt=20=EC=BF=A0?= =?UTF-8?q?=ED=82=A4=EB=A1=9C=20=EB=B3=80=ED=99=98=20&=20swagger=20ui=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 46 +++++++- .../auth/controller/AuthControllerDocs.java | 81 +++++++++++++ .../auth/dto/request/AdminLoginRequest.java | 4 + .../dto/response/AccessTokenResponse.java | 5 + .../domain/auth/service/AuthService.java | 65 +++++++++++ .../controller/ConceptTagController.java | 2 + .../dto/response/ConceptTagResponse.java | 3 + .../member/controller/MemberController.java | 2 + .../controller/PracticeTestTagController.java | 3 + .../problem/controller/ProblemController.java | 2 + .../controller/ProblemSearchController.java | 2 + .../domain/problem/ProblemAdminId.java | 2 +- .../dto/request/ProblemPostRequest.java | 2 +- .../dto/request/ProblemUpdateRequest.java | 2 + .../dto/response/ChildProblemGetResponse.java | 2 + .../response/ConceptTagSearchResponse.java | 5 +- .../dto/response/PracticeTestTagResponse.java | 3 + .../dto/response/ProblemGetResponse.java | 2 + .../response/ProblemSearchGetResponse.java | 8 +- .../controller/ProblemSetController.java | 2 + .../ProblemSetSearchController.java | 9 +- .../publish/controller/PublishController.java | 2 + .../config/security/SecurityConfig.java | 3 +- .../global/error/GlobalExceptionHandler.java | 9 ++ .../handler/EmailPasswordSuccessHandler.java | 6 +- .../global/security/utils/CookieUtil.java | 20 ++++ .../global/security/utils/JwtUtil.java | 9 ++ .../auth/controller/AuthControllerTest.java | 7 +- .../controller/RefreshTokenReissueTest.java | 108 ++++++++++++++++++ 29 files changed, 399 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerDocs.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/auth/dto/response/AccessTokenResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/auth/service/AuthService.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/utils/CookieUtil.java create mode 100644 src/test/java/com/moplus/moplus_server/domain/auth/controller/RefreshTokenReissueTest.java diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java index 353a056..862b52a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java @@ -1,10 +1,20 @@ package com.moplus.moplus_server.domain.auth.controller; import com.moplus.moplus_server.domain.auth.dto.request.AdminLoginRequest; +import com.moplus.moplus_server.domain.auth.dto.response.AccessTokenResponse; import com.moplus.moplus_server.domain.auth.dto.response.TokenResponse; -import io.swagger.v3.oas.annotations.Operation; +import com.moplus.moplus_server.domain.auth.service.AuthService; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; +import com.moplus.moplus_server.global.security.utils.CookieUtil; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,15 +23,41 @@ @RestController @RequestMapping("/api/v1/auth") @RequiredArgsConstructor -public class AuthController { +public class AuthController implements AuthControllerDocs { - @Operation(summary = "어드민 로그인", description = "아아디 패스워드 로그인 후 토큰 발급합니다.") + private final AuthService authService; + private final CookieUtil cookieUtil; + + private static String validateRefreshTokenCookie(HttpServletRequest request) { + if (request.getCookies() == null) { + throw new NotFoundException(ErrorCode.BLANK_INPUT_VALUE); + } + Cookie[] cookies = request.getCookies(); + return Arrays.stream(cookies) + .filter(cookie -> "refreshToken".equals(cookie.getName())) + .map(Cookie::getValue) + .findFirst() + .orElseThrow(() -> new NotFoundException(ErrorCode.BLANK_INPUT_VALUE)); + } + + @Override @PostMapping("/admin/login") - public ResponseEntity<TokenResponse> adminLogin( - @RequestBody AdminLoginRequest request + public ResponseEntity<AccessTokenResponse> adminLogin( + @Valid @RequestBody AdminLoginRequest request ) { // 실제 처리는 Security 필터에서 이루어지며, 이 메서드는 Swagger 명세용입니다. return null; } + @Override + @GetMapping("/reissue") + public ResponseEntity<AccessTokenResponse> reissueToken(HttpServletRequest request, HttpServletResponse response) { + String refreshToken = validateRefreshTokenCookie(request); + + TokenResponse tokenResponse = authService.reissueToken(refreshToken); + + response.addCookie(cookieUtil.createCookie(tokenResponse.refreshToken())); + + return ResponseEntity.ok(new AccessTokenResponse(tokenResponse.accessToken())); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerDocs.java b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerDocs.java new file mode 100644 index 0000000..90a2610 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerDocs.java @@ -0,0 +1,81 @@ +package com.moplus.moplus_server.domain.auth.controller; + +import com.moplus.moplus_server.domain.auth.dto.request.AdminLoginRequest; +import com.moplus.moplus_server.domain.auth.dto.response.AccessTokenResponse; +import com.moplus.moplus_server.global.error.ErrorResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.ResponseEntity; + +@Tag(name = "인증", description = "인증 관련 API") +public interface AuthControllerDocs { + + @Operation( + summary = "어드민 로그인", + description = "이메일과 비밀번호로 로그인하여 액세스 토큰을 발급받고 리프레시 토큰을 쿠키에 설정합니다.", + responses = { + @ApiResponse( + responseCode = "200", + description = "로그인 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = AccessTokenResponse.class) + ), + headers = @Header( + name = "Set-Cookie", + description = "리프레시 토큰이 담긴 HTTP Only 쿠키", + schema = @Schema( + type = "string", + example = "refreshToken=xxx; Path=/; HttpOnly; Secure; SameSite=None" + ) + ) + ), + @ApiResponse( + responseCode = "401", + description = "인증 실패 (잘못된 이메일 또는 비밀번호)", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ) + } + ) + ResponseEntity<AccessTokenResponse> adminLogin(AdminLoginRequest request); + + @Operation( + summary = "토큰 재발급", + description = "리프레시 토큰을 통해 새로운 액세스 토큰을 발급하고 새로운 리프레시 토큰을 쿠키에 설정합니다.", + responses = { + @ApiResponse( + responseCode = "200", + description = "토큰 재발급 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = AccessTokenResponse.class) + ), + headers = @Header( + name = "Set-Cookie", + description = "새로운 리프레시 토큰이 담긴 HTTP Only 쿠키", + schema = @Schema( + type = "string", + example = "refreshToken=xxx; Path=/; HttpOnly; Secure; SameSite=None" + ) + ) + ), + @ApiResponse( + responseCode = "401", + description = "유효하지 않은 리프레시 토큰", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "리프레시 토큰 쿠키 없음", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ) + } + ) + ResponseEntity<AccessTokenResponse> reissueToken(HttpServletRequest request, HttpServletResponse response); +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/dto/request/AdminLoginRequest.java b/src/main/java/com/moplus/moplus_server/domain/auth/dto/request/AdminLoginRequest.java index 861df83..e0c6c14 100644 --- a/src/main/java/com/moplus/moplus_server/domain/auth/dto/request/AdminLoginRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/auth/dto/request/AdminLoginRequest.java @@ -1,7 +1,11 @@ package com.moplus.moplus_server.domain.auth.dto.request; +import jakarta.validation.constraints.NotNull; + public record AdminLoginRequest( + @NotNull(message = "이메일을 입력해주세요.") String email, + @NotNull(message = "비밀번호를 입력해주세요.") String password ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/AccessTokenResponse.java b/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/AccessTokenResponse.java new file mode 100644 index 0000000..f8d70c2 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/AccessTokenResponse.java @@ -0,0 +1,5 @@ +package com.moplus.moplus_server.domain.auth.dto.response; + +public record AccessTokenResponse( + String accessToken +) {} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/service/AuthService.java b/src/main/java/com/moplus/moplus_server/domain/auth/service/AuthService.java new file mode 100644 index 0000000..b11bac9 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/auth/service/AuthService.java @@ -0,0 +1,65 @@ +package com.moplus.moplus_server.domain.auth.service; + +import com.moplus.moplus_server.domain.auth.dto.response.TokenResponse; +import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.domain.member.service.MemberService; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import com.moplus.moplus_server.global.security.exception.JwtInvalidException; +import com.moplus.moplus_server.global.security.utils.JwtUtil; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.security.SignatureException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final JwtUtil jwtUtil; + private final MemberService memberService; + + @Transactional + public TokenResponse reissueToken(String refreshToken) { + if (refreshToken == null) { + throw new InvalidValueException(ErrorCode.INVALID_INPUT_VALUE); + } + + Claims claims = getClaims(refreshToken); + final Member member = getMemberById(claims.getSubject()); + + // 새로운 액세스 토큰과 리프레시 토큰 생성 + String newAccessToken = jwtUtil.generateAccessToken(member); + String newRefreshToken = jwtUtil.generateRefreshToken(member); + + return new TokenResponse(newAccessToken, newRefreshToken); + } + + private Claims getClaims(String refreshToken) { + Claims claims; + try { + claims = jwtUtil.getRefreshTokenClaims(refreshToken); + } catch (ExpiredJwtException expiredJwtException) { + throw new JwtInvalidException(ErrorCode.EXPIRED_TOKEN.getMessage()); + } catch (SignatureException signatureException) { + throw new JwtInvalidException(ErrorCode.WRONG_TYPE_TOKEN.getMessage()); + } catch (MalformedJwtException malformedJwtException) { + throw new JwtInvalidException(ErrorCode.UNSUPPORTED_TOKEN.getMessage()); + } catch (IllegalArgumentException illegalArgumentException) { + throw new JwtInvalidException(ErrorCode.UNKNOWN_ERROR.getMessage()); + } + return claims; + } + + private Member getMemberById(String id) { + try { + return memberService.getMemberById(Long.parseLong(id)); + } catch (Exception e) { + throw new BadCredentialsException(ErrorCode.BAD_CREDENTIALS.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java b/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java index 094e6ae..e575025 100644 --- a/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java +++ b/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.domain.concept.dto.response.ConceptTagResponse; import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -10,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "문항", description = "문항 관련 API") @RestController @RequestMapping("/api/v1/conceptTags") @RequiredArgsConstructor diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/dto/response/ConceptTagResponse.java b/src/main/java/com/moplus/moplus_server/domain/concept/dto/response/ConceptTagResponse.java index e92b490..0f5e5cd 100644 --- a/src/main/java/com/moplus/moplus_server/domain/concept/dto/response/ConceptTagResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/concept/dto/response/ConceptTagResponse.java @@ -1,9 +1,12 @@ package com.moplus.moplus_server.domain.concept.dto.response; import com.moplus.moplus_server.domain.concept.domain.ConceptTag; +import jakarta.validation.constraints.NotNull; public record ConceptTagResponse( + @NotNull(message = "개념 태그 ID는 필수입니다") Long id, + @NotNull(message = "개념 태그 이름은 필수입니다") String name ) { public static ConceptTagResponse of(ConceptTag entity) { diff --git a/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java b/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java index 6fd8166..43a0cb2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java +++ b/src/main/java/com/moplus/moplus_server/domain/member/controller/MemberController.java @@ -4,12 +4,14 @@ import com.moplus.moplus_server.domain.member.dto.response.MemberGetResponse; import com.moplus.moplus_server.global.annotation.AuthUser; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "회원", description = "회원 관련 API") @RestController @RequestMapping("/api/v1/member") @RequiredArgsConstructor diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java index 1534257..ba0703c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/PracticeTestTagController.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.domain.problem.dto.response.PracticeTestTagResponse; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -10,6 +11,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "문항", description = "문항 관련 API") @RestController @RequestMapping("/api/v1/practiceTestTags") @RequiredArgsConstructor diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java index 4d7f2fb..d4acee7 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java @@ -8,6 +8,7 @@ import com.moplus.moplus_server.domain.problem.service.ProblemSaveService; import com.moplus.moplus_server.domain.problem.service.ProblemUpdateService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "문항", description = "문항 관련 API") @RestController @RequestMapping("/api/v1/problems") @RequiredArgsConstructor diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java index 7c8c50d..7ec3490 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.domain.problem.dto.response.ProblemSearchGetResponse; import com.moplus.moplus_server.domain.problem.repository.ProblemSearchRepositoryCustom; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -11,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "문항", description = "문항 관련 API") @RestController @RequestMapping("/api/v1/problems") @RequiredArgsConstructor diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java index a30bb59..7c04c6e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java @@ -12,7 +12,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ProblemAdminId implements Serializable { - @Column(name = "problem_admin_id") + @Column(name = "problem_admin_id", nullable = false) private String id; public ProblemAdminId(String id) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java index c909206..0933dec 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java @@ -7,7 +7,7 @@ import jakarta.validation.constraints.NotNull; public record ProblemPostRequest( - @NotNull(message = "문제 유형은 필수입니다") + @NotNull(message = "문항 유형은 필수입니다") ProblemType problemType, Long practiceTestId, int number diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java index 1dcab23..c55fb83 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java @@ -2,10 +2,12 @@ import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.Set; public record ProblemUpdateRequest( + @NotNull(message = "문제 유형은 필수입니다") ProblemType problemType, Long practiceTestId, int number, diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java index ae7552a..3daed3d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java @@ -2,11 +2,13 @@ import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; +import jakarta.validation.constraints.NotNull; import java.util.Set; import lombok.Builder; @Builder public record ChildProblemGetResponse( + @NotNull(message = "새끼 문항 ID는 필수입니다") Long childProblemId, String imageUrl, AnswerType answerType, diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ConceptTagSearchResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ConceptTagSearchResponse.java index 75e5c73..1229df6 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ConceptTagSearchResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ConceptTagSearchResponse.java @@ -1,13 +1,16 @@ package com.moplus.moplus_server.domain.problem.dto.response; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @NoArgsConstructor public class ConceptTagSearchResponse { + @NotNull(message = "개념 태그 ID는 필수입니다") private Long id; - private String name; // 예시로 태그 이름을 추가 (필요에 따라 변경 가능) + @NotNull(message = "개념 태그 이름은 필수입니다") + private String name; public ConceptTagSearchResponse(Long id, String name) { this.id = id; diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java index a4b0f1c..3f2a344 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PracticeTestTagResponse.java @@ -1,9 +1,12 @@ package com.moplus.moplus_server.domain.problem.dto.response; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; +import jakarta.validation.constraints.NotNull; public record PracticeTestTagResponse( + @NotNull(message = "기출 모의고사 태그 ID는 필수입니다") Long id, + @NotNull(message = "기출 모의고사 태그 이름은 필수입니다") String name ) { public static PracticeTestTagResponse of(PracticeTestTag practiceTestTag) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java index a39c30f..650189b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java @@ -3,12 +3,14 @@ import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.Set; import lombok.Builder; @Builder public record ProblemGetResponse( + @NotNull(message = "문항 ID은 필수입니다") String problemId, Set<Long> conceptTagIds, Long practiceTestId, diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java index 7a1710b..459b45e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java @@ -1,5 +1,6 @@ package com.moplus.moplus_server.domain.problem.dto.response; +import jakarta.validation.constraints.NotNull; import java.util.Set; import lombok.Getter; import lombok.NoArgsConstructor; @@ -7,15 +8,16 @@ @Getter @NoArgsConstructor public class ProblemSearchGetResponse { + @NotNull(message = "문항 ID는 필수입니다") private String problemId; - private String comment; + private String memo; private String mainProblemImageUrl; private Set<ConceptTagSearchResponse> conceptTagResponses; - public ProblemSearchGetResponse(String problemId, String comment, String mainProblemImageUrl, + public ProblemSearchGetResponse(String problemId, String memo, String mainProblemImageUrl, Set<ConceptTagSearchResponse> conceptTagResponses) { this.problemId = problemId; - this.comment = comment; + this.memo = memo; this.mainProblemImageUrl = mainProblemImageUrl; this.conceptTagResponses = conceptTagResponses; } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java index 8b13e35..f38a715 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -11,6 +11,7 @@ import com.moplus.moplus_server.domain.problemset.service.ProblemSetUpdateService; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -22,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "문항세트", description = "문항세트 관련 API") @RestController @RequestMapping("/api/v1/problemSet") @RequiredArgsConstructor diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java index 09c0b11..044c567 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java @@ -4,6 +4,7 @@ import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetSearchRepositoryCustom; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -19,6 +20,7 @@ public class ProblemSetSearchController { private final ProblemSetSearchRepositoryCustom problemSetSearchRepository; + @Tag(name = "문항세트", description = "문항세트 관련 API") @GetMapping("/search") @Operation( summary = "문항세트 검색", @@ -29,10 +31,12 @@ public ResponseEntity<List<ProblemSetSearchGetResponse>> search( @RequestParam(value = "problemTitle", required = false) String problemTitle, @RequestParam(value = "conceptTagNames", required = false) List<String> conceptTagNames ) { - List<ProblemSetSearchGetResponse> problemSets = problemSetSearchRepository.search(problemSetTitle, problemTitle, conceptTagNames); + List<ProblemSetSearchGetResponse> problemSets = problemSetSearchRepository.search(problemSetTitle, problemTitle, + conceptTagNames); return ResponseEntity.ok(problemSets); } + @Tag(name = "발행", description = "발행 관련 API") @GetMapping("/confirm/search") @Operation( summary = "발행용 문항세트 검색", @@ -43,7 +47,8 @@ public ResponseEntity<List<ProblemSetSearchGetResponse>> confirmSearch( @RequestParam(value = "problemTitle", required = false) String problemTitle, @RequestParam(value = "conceptTagNames", required = false) List<String> conceptTagNames ) { - List<ProblemSetSearchGetResponse> problemSets = problemSetSearchRepository.confirmSearch(problemSetTitle, problemTitle, conceptTagNames); + List<ProblemSetSearchGetResponse> problemSets = problemSetSearchRepository.confirmSearch(problemSetTitle, + problemTitle, conceptTagNames); return ResponseEntity.ok(problemSets); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java index a703911..924344e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java @@ -6,6 +6,7 @@ import com.moplus.moplus_server.domain.publish.service.PublishGetService; import com.moplus.moplus_server.domain.publish.service.PublishSaveService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "발행", description = "발행 관련 API") @RestController @RequestMapping("/api/v1/publish") @RequiredArgsConstructor diff --git a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java index 4582202..62928cd 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java @@ -36,7 +36,8 @@ public class SecurityConfig { private final EmailPasswordSuccessHandler emailPasswordSuccessHandler; private final JwtUtil jwtUtil; - private String[] allowUrls = {"/", "/favicon.ico", "/swagger-ui/**", "/v3/**", "/actuator/**"}; + private String[] allowUrls = {"/", "/favicon.ico", "/swagger-ui/**", "/v3/**", "/actuator/**", + "/api/v1/auth/reissue"}; @Value("${cors-allowed-origins}") private List<String> corsAllowedOrigins; diff --git a/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java b/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java index 9e163aa..2e97cd6 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java +++ b/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.global.error.exception.BusinessException; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; +import com.moplus.moplus_server.global.security.exception.JwtInvalidException; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.springframework.context.support.DefaultMessageSourceResolvable; @@ -69,4 +70,12 @@ protected ResponseEntity<ErrorResponse> handleException(final Exception exceptio final ErrorResponse response = ErrorResponse.from(ErrorCode.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(response, response.getStatus()); } + + @ExceptionHandler(JwtInvalidException.class) + protected ResponseEntity<ErrorResponse> handleJwtInvalidException(final JwtInvalidException exception) { + log.error("handleJwtInvalidException", exception); + final ErrorResponse response = ErrorResponse.from(ErrorCode.BAD_CREDENTIALS); + + return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED); + } } diff --git a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java index f5ebad6..67db246 100644 --- a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java +++ b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.global.security.utils.CookieUtil; import com.moplus.moplus_server.global.security.utils.JwtUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -18,6 +19,7 @@ public class EmailPasswordSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private final JwtUtil jwtUtil; + private final CookieUtil cookieUtil; private final ObjectMapper objectMapper = new ObjectMapper(); // JSON 변환을 위한 ObjectMapper @Override @@ -27,10 +29,10 @@ public void onAuthenticationSuccess(final HttpServletRequest request, final Http String accessToken = jwtUtil.generateAccessToken(member); String refreshToken = jwtUtil.generateRefreshToken(member); - // JSON 응답 생성 + response.addCookie(cookieUtil.createCookie(refreshToken)); + Map<String, String> tokenResponse = new HashMap<>(); tokenResponse.put("accessToken", accessToken); - tokenResponse.put("refreshToken", refreshToken); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); diff --git a/src/main/java/com/moplus/moplus_server/global/security/utils/CookieUtil.java b/src/main/java/com/moplus/moplus_server/global/security/utils/CookieUtil.java new file mode 100644 index 0000000..aa7305e --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/utils/CookieUtil.java @@ -0,0 +1,20 @@ +package com.moplus.moplus_server.global.security.utils; + +import jakarta.servlet.http.Cookie; +import org.springframework.stereotype.Component; + +@Component +public class CookieUtil { + + public Cookie createCookie(String refreshToken) { + String cookieName = "refreshToken"; + Cookie cookie = new Cookie(cookieName, refreshToken); + + cookie.setHttpOnly(true); + cookie.setSecure(true); + cookie.setPath("/"); + cookie.setMaxAge(60 * 60 * 24 * 7); //일주일 + cookie.setAttribute("SameSite", "None"); + return cookie; + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java b/src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java index d282029..cc80237 100644 --- a/src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java +++ b/src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java @@ -58,6 +58,15 @@ public Claims getAccessTokenClaims(Authentication authentication) { .getBody(); } + public Claims getRefreshTokenClaims(String refreshToken) { + return Jwts.parserBuilder() + .requireIssuer(jwtProperties.issuer()) + .setSigningKey(getRefreshTokenKey()) + .build() + .parseClaimsJws(refreshToken) + .getBody(); + } + private Key getAccessTokenKey() { return Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes()); } diff --git a/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java b/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java index 4d87cb3..d8f3c5e 100644 --- a/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.domain.auth.controller; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -58,7 +59,11 @@ class 어드민_로그인 { .content(requestBody)) .andExpect(status().isOk()) // HTTP 200 응답 확인 .andExpect(jsonPath("$.accessToken").isNotEmpty()) // accessToken 필드 존재 여부 확인 - .andExpect(jsonPath("$.refreshToken").isNotEmpty()); // refreshToken 필드 존재 여부 확인 + .andExpect(cookie().exists("refreshToken")) // 리프레시 토큰 쿠키 존재 확인 + .andExpect(cookie().httpOnly("refreshToken", true)) // HTTP Only 설정 확인 + .andExpect(cookie().secure("refreshToken", true)) // Secure 설정 확인 + .andExpect(cookie().path("refreshToken", "/")) // 쿠키 경로 확인 + .andExpect(cookie().attribute("refreshToken", "SameSite", "None")); } diff --git a/src/test/java/com/moplus/moplus_server/domain/auth/controller/RefreshTokenReissueTest.java b/src/test/java/com/moplus/moplus_server/domain/auth/controller/RefreshTokenReissueTest.java new file mode 100644 index 0000000..0bbecfb --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/auth/controller/RefreshTokenReissueTest.java @@ -0,0 +1,108 @@ +package com.moplus.moplus_server.domain.auth.controller; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.moplus.moplus_server.global.properties.jwt.JwtProperties; +import com.moplus.moplus_server.global.security.utils.CookieUtil; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.Cookie; +import java.security.Key; +import java.util.Date; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("h2test") +@Sql({"/auth-test-data.sql"}) +public class RefreshTokenReissueTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private JwtProperties jwtProperties; + + @Autowired + private CookieUtil cookieUtil; + + private String validRefreshToken; + + @BeforeEach + public void setup() { + + // Generate a test token + Key key = Keys.hmacShaKeyFor(jwtProperties.refreshTokenSecret().getBytes()); + Date issuedAt = new Date(); // 3 hour ago + Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.refreshTokenExpirationMilliTime()); + validRefreshToken = Jwts.builder() + .setIssuer(jwtProperties.issuer()) + .setSubject("1") + .claim("role", "ROLE_USER") + .setIssuedAt(issuedAt) + .setExpiration(expiredAt) + .signWith(key) + .compact(); + } + + @Nested + class 토큰_재발급 { + + @Test + void 성공() throws Exception { + // given + Cookie refreshTokenCookie = cookieUtil.createCookie(validRefreshToken); + + // when & then + mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/auth/reissue") + .cookie(refreshTokenCookie)) + .andExpect(status().isOk()) + .andExpect(cookie().exists("refreshToken")) + .andExpect(cookie().httpOnly("refreshToken", true)) + .andExpect(cookie().secure("refreshToken", true)) + .andExpect(cookie().path("refreshToken", "/")) + .andExpect(cookie().attribute("refreshToken", "SameSite", "None")); + } + + @Test + void 실패_리프레시토큰_없음() throws Exception { + // when & then + mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/auth/reissue")) + .andExpect(status().isUnauthorized()); + } + + @Test + void 실패_유효하지_않은_리프레시토큰() throws Exception { + // given + Cookie invalidRefreshTokenCookie = new Cookie("refreshToken", "invalid_refresh_token"); + + // when & then + mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/auth/reissue") + .cookie(invalidRefreshTokenCookie)) + .andExpect(status().isUnauthorized()); + } + + @Test + void 실패_만료된_리프레시토큰() throws Exception { + // given + Cookie expiredRefreshTokenCookie = new Cookie("refreshToken", "expired_refresh_token"); + + // when & then + mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/auth/reissue") + .cookie(expiredRefreshTokenCookie)) + .andExpect(status().isUnauthorized()); + } + } +} From 59a7a07a4f95b03eef3a4ec11e17c988bff26978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:07:15 +0900 Subject: [PATCH 078/115] =?UTF-8?q?[refactor/#48]=20refresh=20=EC=BF=A0?= =?UTF-8?q?=ED=82=A4=20=EC=97=86=EC=9D=84=20=EB=95=8C=20400=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problem/service/mapper/ProblemMapperImpl.java | 2 +- .../domain/auth/controller/RefreshTokenReissueTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index cf20a4a..dbb174c 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -14,7 +14,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-13T05:08:28+0900", + date = "2025-02-13T19:05:39+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component diff --git a/src/test/java/com/moplus/moplus_server/domain/auth/controller/RefreshTokenReissueTest.java b/src/test/java/com/moplus/moplus_server/domain/auth/controller/RefreshTokenReissueTest.java index 0bbecfb..6349475 100644 --- a/src/test/java/com/moplus/moplus_server/domain/auth/controller/RefreshTokenReissueTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/auth/controller/RefreshTokenReissueTest.java @@ -80,7 +80,7 @@ class 토큰_재발급 { void 실패_리프레시토큰_없음() throws Exception { // when & then mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/auth/reissue")) - .andExpect(status().isUnauthorized()); + .andExpect(status().is4xxClientError()); } @Test From d874a7d2f852f15fc279573d01dbd1e9dc909c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 14 Feb 2025 02:56:45 +0900 Subject: [PATCH 079/115] =?UTF-8?q?[feat/#50]=20presigned=20url=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ImageUploadController.java | 39 +++++++++++++++++++ .../domain/problem/ProblemImageType.java | 19 +++++++++ .../problem/repository/ProblemRepository.java | 6 +++ .../problem/service/ImageUploadService.java | 39 +++++++++++++++++++ .../global/error/exception/ErrorCode.java | 8 ++-- .../moplus_server/global/utils/s3/S3Util.java | 22 +++++++++++ 6 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemImageType.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java new file mode 100644 index 0000000..66bfc71 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java @@ -0,0 +1,39 @@ +package com.moplus.moplus_server.domain.problem.controller; + +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemImageType; +import com.moplus.moplus_server.domain.problem.service.ImageUploadService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "이미지 업로드 API", description = "이미지 업로드 관련 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/images") +public class ImageUploadController { + + private final ImageUploadService imageUploadService; + + @Operation(summary = "이미지 업로드를 위한 presigned URL 발급") + @GetMapping("/problem/{problemId}/presigned-url") + public ResponseEntity<String> getProblemImagePresignedUrl( + @PathVariable("problemId") String problemId, + @RequestParam(value = "image-type") ProblemImageType imageType) { + String presignedUrl = imageUploadService.generateProblemImagePresignedUrl(problemId, imageType); + return ResponseEntity.ok(presignedUrl); + } + + @Operation(summary = "이미지 업로드 완료 후 URL 조회") + @GetMapping("/{fileName}") + public ResponseEntity<String> getImageUrl( + @PathVariable("fileName") String fileName) { + String imageUrl = imageUploadService.getImageUrl(fileName); + return ResponseEntity.ok(imageUrl); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemImageType.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemImageType.java new file mode 100644 index 0000000..305bb72 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemImageType.java @@ -0,0 +1,19 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ProblemImageType { + MAIN_PROBLEM("main-problem", "문제 이미지"), + MAIN_ANALYSIS("main-analysis", "분석 이미지"), + MAIN_HANDWRITING_EXPLANATION("main-handwriting-explanation", "손글씨 설명 이미지"), + READING_TIP("reading-tip", "읽기 팁 이미지"), + SENIOR_TIP("senior-tip", "선배 팁 이미지"), + PRESCRIPTION("prescription", "처방전 이미지"), + CHILD_PROBLEM("child-problem", "하위 문제 이미지"); + + private final String type; + private final String description; +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java index 04e7d94..6f62f77 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java @@ -16,6 +16,12 @@ default void existsByIdElseThrow(Long id) { } } + default void existsByProblemAdminIdElseThrow(ProblemAdminId problemAdminId) { + if (!existsByProblemAdminId(problemAdminId)) { + throw new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND); + } + } + default Problem findByIdElseThrow(Long id) { return findById(id).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java new file mode 100644 index 0000000..9617adf --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java @@ -0,0 +1,39 @@ +package com.moplus.moplus_server.domain.problem.service; + +import com.amazonaws.HttpMethod; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemImageType; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.global.utils.s3.S3Util; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ImageUploadService { + + private static final String PROBLEM_IMAGE_PREFIX = "problems/"; + private final S3Util s3Util; + private final ProblemRepository problemRepository; + + public String generateProblemImagePresignedUrl(String problemId, ProblemImageType imageType) { + problemRepository.existsByProblemAdminIdElseThrow(new ProblemAdminId(problemId)); + String fileName = generateProblemImageFileName(problemId, imageType); + return s3Util.getS3PresignedUrl(fileName, HttpMethod.PUT); + } + + private String generateProblemImageFileName(String problemId, ProblemImageType imageType) { + String uuid = UUID.randomUUID().toString(); + return String.format("%s%s/%s/%s.jpg", + PROBLEM_IMAGE_PREFIX, + problemId, + imageType.getType(), + uuid + ); + } + + public String getImageUrl(String fileName) { + return s3Util.getS3ObjectUrl(fileName); + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 25f5519..199a216 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -28,10 +28,10 @@ public enum ErrorCode { PRACTICE_TEST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 모의고사를 찾을 수 없습니다"), //문항 - PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 문제를 찾을 수 없습니다"), - PROBLEM_ALREADY_EXIST(HttpStatus.CONFLICT, "해당 문제는 이미 존재합니다"), - INVALID_MULTIPLE_CHOICE_ANSWER(HttpStatus.BAD_REQUEST, "객관식 문제의 정답은 1~5 사이의 숫자여야 합니다"), - INVALID_SHORT_NUMBER_ANSWER(HttpStatus.BAD_REQUEST, "주관식 문제의 정답은 0~999 사이의 숫자여야 합니다"), + PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 문항을 찾을 수 없습니다"), + PROBLEM_ALREADY_EXIST(HttpStatus.CONFLICT, "해당 문항는 이미 존재합니다"), + INVALID_MULTIPLE_CHOICE_ANSWER(HttpStatus.BAD_REQUEST, "객관식 문항의 정답은 1~5 사이의 숫자여야 합니다"), + INVALID_SHORT_NUMBER_ANSWER(HttpStatus.BAD_REQUEST, "주관식 문항의 정답은 0~999 사이의 숫자여야 합니다"), INVALID_CONFIRM_PROBLEM(HttpStatus.BAD_REQUEST, "유효하지 않은 문항들 : "), INVALID_DIFFICULTY(HttpStatus.BAD_REQUEST, "난이도는 1~10 사이의 숫자여야 합니다"), diff --git a/src/main/java/com/moplus/moplus_server/global/utils/s3/S3Util.java b/src/main/java/com/moplus/moplus_server/global/utils/s3/S3Util.java index d2abde8..68189f1 100644 --- a/src/main/java/com/moplus/moplus_server/global/utils/s3/S3Util.java +++ b/src/main/java/com/moplus/moplus_server/global/utils/s3/S3Util.java @@ -1,10 +1,13 @@ package com.moplus.moplus_server.global.utils.s3; +import com.amazonaws.HttpMethod; import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import com.amazonaws.services.s3.model.PutObjectRequest; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.io.File; +import java.util.Date; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -29,5 +32,24 @@ public String getS3ObjectUrl(String fileName) { return amazonS3.getUrl(bucketName, fileName).toString(); } + public String getS3PresignedUrl(String fileName, HttpMethod httpMethod) { + + GeneratePresignedUrlRequest generatePresignedUrlRequest = + new GeneratePresignedUrlRequest(bucketName, fileName) + .withMethod(httpMethod) + .withExpiration(getPreSignedUrlExpiration()); + + return amazonS3.generatePresignedUrl(generatePresignedUrlRequest).toString(); + } + + private Date getPreSignedUrlExpiration() { + final int PRESIGNED_EXPIRATION = 1000 * 60 * 30; //30분 + + Date expiration = new Date(); + var expTimeMillis = expiration.getTime(); + expTimeMillis += PRESIGNED_EXPIRATION; + expiration.setTime(expTimeMillis); + return expiration; + } } From 011573dd48f6d16ec56bb4d6acadfdde1b4236e8 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Sat, 15 Feb 2025 13:01:54 +0900 Subject: [PATCH 080/115] =?UTF-8?q?[feat/#47]=20null=EA=B0=92=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moplus_server/domain/problemset/domain/ProblemSet.java | 4 +++- .../domain/publish/controller/PublishController.java | 3 ++- .../domain/publish/dto/request/PublishPostRequest.java | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index 43d09db..7a99cfd 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -35,15 +35,17 @@ public class ProblemSet extends BaseEntity { @Embedded private Title title; + @Column(nullable = false) private boolean isDeleted; @Enumerated(EnumType.STRING) + @Column(nullable = false) private ProblemSetConfirmStatus confirmStatus; @ElementCollection @CollectionTable(name = "problem_set_problems", joinColumns = @JoinColumn(name = "problem_set_id")) - @Column(name = "problem_id") @OrderColumn(name = "sequence") + @Column(name = "problem_id", nullable = false) private List<Long> problemIds = new ArrayList<>(); @Builder diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java index 924344e..0415f17 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java @@ -7,6 +7,7 @@ import com.moplus.moplus_server.domain.publish.service.PublishSaveService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -40,7 +41,7 @@ public ResponseEntity<List<PublishMonthGetResponse>> getPublishMonth( @PostMapping("") @Operation(summary = "발행 생성하기", description = "특정 날짜에 문항세트를 발행합니다.") public ResponseEntity<Long> postPublish( - @RequestBody PublishPostRequest request + @Valid @RequestBody PublishPostRequest request ) { return ResponseEntity.ok(publishSaveService.createPublish(request)); } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java index a020f43..7d3960e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java @@ -1,10 +1,13 @@ package com.moplus.moplus_server.domain.publish.dto.request; import com.moplus.moplus_server.domain.publish.domain.Publish; +import jakarta.validation.constraints.NotNull; import java.time.LocalDate; public record PublishPostRequest( + @NotNull LocalDate publishedDate, + @NotNull Long problemSetId ) { public Publish toEntity() { From 7802a19be12c5a832327275c8c72d832949a680c Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Sat, 15 Feb 2025 13:18:42 +0900 Subject: [PATCH 081/115] =?UTF-8?q?[feat/#47]=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EA=B2=80=EC=83=89=20=EC=8B=9C=20=EB=AC=B8?= =?UTF-8?q?=ED=95=AD=ED=83=80=EC=9D=B4=ED=8B=80,=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=EB=A9=94=EB=AA=A8=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problemset/dto/response/ProblemThumbnailResponse.java | 6 +++++- .../repository/ProblemSetSearchRepositoryCustom.java | 4 ++++ .../repository/ProblemSetSearchRepositoryCustomTest.java | 7 ++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java index 0848eba..2cbef8f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemThumbnailResponse.java @@ -6,9 +6,13 @@ @Getter @NoArgsConstructor public class ProblemThumbnailResponse { + private String problemTitle; + private String problemMemo; private String mainProblemImageUrl; - public ProblemThumbnailResponse(String mainProblemImageUrl) { + public ProblemThumbnailResponse(String problemTitle, String problemMemo, String mainProblemImageUrl) { + this.problemTitle = problemTitle; + this.problemMemo = problemMemo; this.mainProblemImageUrl = mainProblemImageUrl; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index 7fc901c..9faa3f1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -42,6 +42,8 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p publish.publishedDate, // 발행되지 않은 경우 null 반환 GroupBy.list( Projections.constructor(ProblemThumbnailResponse.class, + problem.title.title, + problem.memo, problem.mainProblemImageUrl ) ) @@ -70,6 +72,8 @@ public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, S publish.publishedDate, // 발행되지 않은 경우 null 반환 GroupBy.list( Projections.constructor(ProblemThumbnailResponse.class, + problem.title.title, + problem.memo, problem.mainProblemImageUrl ) ) diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index 4a34089..bffb84d 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -98,8 +98,13 @@ public class ProblemSetSearchRepositoryCustomTest { List<ProblemThumbnailResponse> problems = response.getProblemThumbnailResponses(); assertThat(problems).hasSize(2); - // ✅ 문항의 이미지 URL이 올바르게 매핑되었는지 확인 + // ✅ 문항의 타이틀, 메모, 이미지 URL이 올바르게 매핑되었는지 확인 + assertThat(problems.get(0).getProblemTitle()).isEqualTo("제목1"); + assertThat(problems.get(0).getProblemMemo()).isEqualTo("기존 문제 설명 1"); assertThat(problems.get(0).getMainProblemImageUrl()).isEqualTo("mainProblem.png1"); + + assertThat(problems.get(1).getProblemTitle()).isEqualTo("제목2"); + assertThat(problems.get(1).getProblemMemo()).isEqualTo("기존 문제 설명 2"); assertThat(problems.get(1).getMainProblemImageUrl()).isEqualTo("mainProblem.png2"); } From 39be34dc002ec55651fb6b0591d3b8648877fba9 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Mon, 17 Feb 2025 01:11:02 +0900 Subject: [PATCH 082/115] =?UTF-8?q?[feat/#55]=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=96=B4=EB=93=9C=EB=B0=94=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/response/ResponseDto.java | 20 ++++++++++ .../global/response/ResponseDtoAdvice.java | 39 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java create mode 100644 src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java diff --git a/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java b/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java new file mode 100644 index 0000000..545e075 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java @@ -0,0 +1,20 @@ +package com.moplus.moplus_server.global.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.moplus.moplus_server.global.error.ErrorResponse; +import org.springframework.http.HttpStatus; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record ResponseDto<T>( + T data, + String message, + HttpStatus status +) { + public static <T> ResponseDto<T> success(final T data) { + return new ResponseDto<>(data, null, null); + } + + public static <T> ResponseDto<T> fail(ErrorResponse errorResponse) { + return new ResponseDto<>(null, errorResponse.getMessage(), errorResponse.getStatus()); + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java b/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java new file mode 100644 index 0000000..af24122 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java @@ -0,0 +1,39 @@ +package com.moplus.moplus_server.global.response; + +import com.moplus.moplus_server.global.error.ErrorResponse; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +@RestControllerAdvice( + basePackages = "com.moplus.moplus_server" +) + +public class ResponseDtoAdvice implements ResponseBodyAdvice<Object> { + + @Override + public boolean supports(MethodParameter returnType, Class converterType) { + return !(returnType.getParameterType() == ResponseDto.class) + && MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType); + } + + @Override + public Object beforeBodyWrite( + Object body, + MethodParameter returnType, + MediaType selectedContentType, + Class selectedConverterType, + ServerHttpRequest request, + ServerHttpResponse response + ) { + if (body instanceof ErrorResponse) { + return ResponseDto.fail((ErrorResponse) body); + } + return ResponseDto.success(body); + } +} From 1ad4958b7113ba3cd9d572eca375680306607ecc Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Mon, 17 Feb 2025 01:11:39 +0900 Subject: [PATCH 083/115] =?UTF-8?q?[fix/#55]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=80=EC=83=89=20=EB=B3=80=EA=B2=BD=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProblemSetSearchController.java | 11 +++---- .../ProblemSetSearchRepositoryCustom.java | 16 +++------- .../ProblemSetSearchRepositoryCustomTest.java | 30 +++++-------------- 3 files changed, 16 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java index 044c567..0b65a47 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java @@ -28,11 +28,9 @@ public class ProblemSetSearchController { ) public ResponseEntity<List<ProblemSetSearchGetResponse>> search( @RequestParam(value = "problemSetTitle", required = false) String problemSetTitle, - @RequestParam(value = "problemTitle", required = false) String problemTitle, - @RequestParam(value = "conceptTagNames", required = false) List<String> conceptTagNames + @RequestParam(value = "problemTitle", required = false) String problemTitle ) { - List<ProblemSetSearchGetResponse> problemSets = problemSetSearchRepository.search(problemSetTitle, problemTitle, - conceptTagNames); + List<ProblemSetSearchGetResponse> problemSets = problemSetSearchRepository.search(problemSetTitle, problemTitle); return ResponseEntity.ok(problemSets); } @@ -44,11 +42,10 @@ public ResponseEntity<List<ProblemSetSearchGetResponse>> search( ) public ResponseEntity<List<ProblemSetSearchGetResponse>> confirmSearch( @RequestParam(value = "problemSetTitle", required = false) String problemSetTitle, - @RequestParam(value = "problemTitle", required = false) String problemTitle, - @RequestParam(value = "conceptTagNames", required = false) List<String> conceptTagNames + @RequestParam(value = "problemTitle", required = false) String problemTitle ) { List<ProblemSetSearchGetResponse> problemSets = problemSetSearchRepository.confirmSearch(problemSetTitle, - problemTitle, conceptTagNames); + problemTitle); return ResponseEntity.ok(problemSets); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index 9faa3f1..b93d688 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -22,8 +22,7 @@ public class ProblemSetSearchRepositoryCustom { private final JPAQueryFactory queryFactory; - public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String problemTitle, - List<String> conceptTagNames) { + public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String problemTitle) { return queryFactory .from(problemSet) .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 @@ -31,8 +30,7 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( containsProblemSetTitle(problemSetTitle), - containsProblemTitle(problemTitle), - containsConceptTagNames(conceptTagNames) + containsProblemTitle(problemTitle) ) .distinct() .transform(GroupBy.groupBy(problemSet.id).list( @@ -51,8 +49,7 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p )); } - public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, String problemTitle, - List<String> conceptTagNames) { + public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, String problemTitle) { return queryFactory .from(problemSet) .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 @@ -61,8 +58,7 @@ public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, S .where( problemSet.confirmStatus.eq(CONFIRMED), containsProblemSetTitle(problemSetTitle), - containsProblemTitle(problemTitle), - containsConceptTagNames(conceptTagNames) + containsProblemTitle(problemTitle) ) .distinct() .transform(GroupBy.groupBy(problemSet.id).list( @@ -89,8 +85,4 @@ private BooleanExpression containsProblemSetTitle(String problemSetTitle) { private BooleanExpression containsProblemTitle(String problemTitle) { return (problemTitle == null || problemTitle.isEmpty()) ? null : problem.memo.containsIgnoreCase(problemTitle); } - - private BooleanExpression containsConceptTagNames(List<String> conceptTagNames) { - return (conceptTagNames == null || conceptTagNames.isEmpty()) ? null : conceptTag.name.in(conceptTagNames); - } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index bffb84d..33bc144 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -36,7 +36,7 @@ public class ProblemSetSearchRepositoryCustomTest { @Test void 문항세트_타이틀_일부_포함_검색() { // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고2 모의고사", null, null); + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고2 모의고사", null); // then assertThat(result).hasSize(1); @@ -46,18 +46,7 @@ public class ProblemSetSearchRepositoryCustomTest { @Test void 문항타이틀_포함_검색() { // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, "설명", null); - - // then - assertThat(result).hasSize(2); - assertThat(result.get(0).getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); - assertThat(result.get(1).getProblemSetTitle()).isEqualTo("2025년 5월 고3 모의고사 문제 세트"); - } - - @Test - void 개념태그_하나라도_포함되면_조회() { - // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, null, List.of("미분 개념")); + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, "설명"); // then assertThat(result).hasSize(2); @@ -68,7 +57,7 @@ public class ProblemSetSearchRepositoryCustomTest { @Test void 모두_적용된_검색() { // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고2", "설명 1", List.of("미분 개념")); + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고2", "설명 1"); // then assertThat(result).hasSize(1); @@ -78,7 +67,7 @@ public class ProblemSetSearchRepositoryCustomTest { @Test void 아무_조건도_없으면_모든_데이터_조회() { // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, null, null); + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, null); // then assertThat(result).hasSize(2); @@ -87,7 +76,7 @@ public class ProblemSetSearchRepositoryCustomTest { @Test void 문항_여러개_문항세트_검색_조회() { // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고2 모의고사", null, null); + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고2 모의고사", null); // then assertThat(result).hasSize(1); @@ -111,7 +100,7 @@ public class ProblemSetSearchRepositoryCustomTest { @Test void 발행되지_않은_문항세트는_NOT_CONFIRMED_테스트() { // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고2 모의고사", null, null); + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고2 모의고사", null); // then assertThat(result).hasSize(1); @@ -128,7 +117,7 @@ public class ProblemSetSearchRepositoryCustomTest { publishSaveService.createPublish(new PublishPostRequest(publishDate, 2L)); // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고3 모의고사", null, null); + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고3 모의고사", null); // then assertThat(result).hasSize(1); @@ -146,8 +135,7 @@ public class ProblemSetSearchRepositoryCustomTest { // when: publishSearch 실행 (CONFIRMED 상태만 검색되어야 함) List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.confirmSearch( "고", - "설명", - List.of("미분 개념") + "설명" ); // then @@ -164,7 +152,6 @@ public class ProblemSetSearchRepositoryCustomTest { // when: 발행된 문제 세트만 조회하는 publishSearch 실행 List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.confirmSearch( - null, null, null ); @@ -182,7 +169,6 @@ public class ProblemSetSearchRepositoryCustomTest { // when List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.confirmSearch( "고3 모의고사", - null, null ); From 7997431c4e153f27e31a461ea55e7d8b32c3d3d5 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Mon, 17 Feb 2025 01:27:04 +0900 Subject: [PATCH 084/115] =?UTF-8?q?[fix/#55]=20=EB=B3=80=EA=B2=BD=EB=90=9C?= =?UTF-8?q?=20data=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberControllerTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java b/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java index 2ce90a3..3d3622d 100644 --- a/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java @@ -70,9 +70,9 @@ public void setMockMvc() throws Exception { .contentType("application/json") .header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)) .andExpect(status().isOk()) // 200 응답 확인 - .andExpect(jsonPath("$.id").exists()) // MemberGetResponse의 필드 확인 - .andExpect(jsonPath("$.name").exists()) - .andExpect(jsonPath("$.email").exists()); + .andExpect(jsonPath("$.data.id").exists()) // MemberGetResponse의 필드 확인 + .andExpect(jsonPath("$.data.name").exists()) + .andExpect(jsonPath("$.data.email").exists()); } } } \ No newline at end of file From 1c88774a23751a6803a56af367b09a287c584937 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Mon, 17 Feb 2025 23:26:48 +0900 Subject: [PATCH 085/115] =?UTF-8?q?[hotfix]=20presigned=20url=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problem/controller/ImageUploadController.java | 5 +++-- .../problem/dto/response/PresignedUrlResponse.java | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java index 66bfc71..89bdc0f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.domain.problem.controller; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemImageType; +import com.moplus.moplus_server.domain.problem.dto.response.PresignedUrlResponse; import com.moplus.moplus_server.domain.problem.service.ImageUploadService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -22,11 +23,11 @@ public class ImageUploadController { @Operation(summary = "이미지 업로드를 위한 presigned URL 발급") @GetMapping("/problem/{problemId}/presigned-url") - public ResponseEntity<String> getProblemImagePresignedUrl( + public ResponseEntity<PresignedUrlResponse> getProblemImagePresignedUrl( @PathVariable("problemId") String problemId, @RequestParam(value = "image-type") ProblemImageType imageType) { String presignedUrl = imageUploadService.generateProblemImagePresignedUrl(problemId, imageType); - return ResponseEntity.ok(presignedUrl); + return ResponseEntity.ok(PresignedUrlResponse.of(presignedUrl)); } @Operation(summary = "이미지 업로드 완료 후 URL 조회") diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java new file mode 100644 index 0000000..64b1011 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java @@ -0,0 +1,11 @@ +package com.moplus.moplus_server.domain.problem.dto.response; + +public record PresignedUrlResponse( + String data +) { + public static PresignedUrlResponse of(String presignedUrl) { + return new PresignedUrlResponse( + presignedUrl + ); + } +} From 77dee78c87d99e34b116d098b091ac568e33f361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 18 Feb 2025 18:08:43 +0900 Subject: [PATCH 086/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/childProblem/QChildProblem.java | 2 - .../problem/domain/problem/QProblem.java | 4 +- .../domain/problem/QProblemAdminId.java | 37 ----------- .../domain/problem/QProblemCustomId.java | 37 +++++++++++ .../mapper/ChildProblemMapperImpl.java | 5 +- .../service/mapper/ProblemMapperImpl.java | 16 ++--- .../TestResult/entity/QIncorrectProblem.java | 2 +- .../domain/QProblemImageForTest.java | 2 +- .../dto/response/AccessTokenResponse.java | 8 ++- .../controller/ConceptTagController.java | 2 +- .../problem/controller/ProblemController.java | 32 +++++++-- .../controller/ProblemSearchController.java | 5 +- .../domain/childProblem/ChildProblem.java | 19 ++++-- .../problem/domain/problem/Problem.java | 51 +++++---------- .../domain/problem/ProblemAdminIdService.java | 6 +- ...oblemAdminId.java => ProblemCustomId.java} | 6 +- .../request/ChildProblemUpdateRequest.java | 5 +- .../dto/request/ProblemPostRequest.java | 6 +- .../dto/request/ProblemUpdateRequest.java | 3 +- .../dto/response/ProblemGetResponse.java | 10 ++- .../problem/repository/ProblemRepository.java | 8 +-- .../ProblemSearchRepositoryCustom.java | 8 +-- .../problem/service/ChildProblemService.java | 38 +++++++++++ .../problem/service/ImageUploadService.java | 4 +- .../problem/service/ProblemSaveService.java | 6 +- .../problem/service/ProblemUpdateService.java | 21 ++---- .../service/mapper/ChildProblemMapper.java | 2 + .../problem/service/mapper/ProblemMapper.java | 10 +-- .../controller/ProblemSetController.java | 5 +- .../domain/problemset/domain/ProblemSet.java | 3 +- .../dto/response/ProblemSummaryResponse.java | 2 +- .../publish/controller/PublishController.java | 5 +- .../global/config/WebConfig.java | 7 +- .../global/error/exception/ErrorCode.java | 4 ++ .../global/response/IdResponse.java | 6 ++ .../global/response/ResponseDto.java | 20 ------ .../global/response/ResponseDtoAdvice.java | 39 ----------- .../resources/templates/imageUploadPage.html | 4 +- .../controller/MemberControllerTest.java | 6 +- ...t.java => ProblemCustomIdServiceTest.java} | 14 ++-- .../service/ProblemUpdateServiceTest.java | 65 ++++++------------- src/test/resources/insert-problem.sql | 4 +- src/test/resources/insert-problem2.sql | 4 +- 43 files changed, 258 insertions(+), 285 deletions(-) delete mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemCustomId.java rename src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/{ProblemAdminId.java => ProblemCustomId.java} (71%) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/service/ChildProblemService.java create mode 100644 src/main/java/com/moplus/moplus_server/global/response/IdResponse.java delete mode 100644 src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java delete mode 100644 src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java rename src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/{ProblemAdminIdServiceTest.java => ProblemCustomIdServiceTest.java} (83%) diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java index b22ac5b..193076d 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java @@ -37,8 +37,6 @@ public class QChildProblem extends EntityPathBase<ChildProblem> { public final StringPath imageUrl = createString("imageUrl"); - public final NumberPath<Integer> sequence = createNumber("sequence", Integer.class); - //inherited public final DateTimePath<java.time.LocalDateTime> updatedDate = _super.updatedDate; diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java index 74bade6..4850572 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java @@ -55,7 +55,7 @@ public class QProblem extends EntityPathBase<Problem> { public final ListPath<String, StringPath> prescriptionImageUrls = this.<String, StringPath>createList("prescriptionImageUrls", String.class, StringPath.class, PathInits.DIRECT2); - public final QProblemAdminId problemAdminId; + public final QProblemCustomId problemCustomId; public final EnumPath<ProblemType> problemType = createEnum("problemType", ProblemType.class); @@ -88,7 +88,7 @@ public QProblem(Class<? extends Problem> type, PathMetadata metadata, PathInits super(type, metadata, inits); this.answer = inits.isInitialized("answer") ? new com.moplus.moplus_server.domain.problem.domain.QAnswer(forProperty("answer")) : null; this.difficulty = inits.isInitialized("difficulty") ? new QDifficulty(forProperty("difficulty")) : null; - this.problemAdminId = inits.isInitialized("problemAdminId") ? new QProblemAdminId(forProperty("problemAdminId")) : null; + this.problemCustomId = inits.isInitialized("problemCustomId") ? new QProblemCustomId(forProperty("problemCustomId")) : null; this.title = inits.isInitialized("title") ? new QTitle(forProperty("title")) : null; } diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java deleted file mode 100644 index d7e56ee..0000000 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.moplus.moplus_server.domain.problem.domain.problem; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QProblemAdminId is a Querydsl query type for ProblemAdminId - */ -@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") -public class QProblemAdminId extends BeanPath<ProblemAdminId> { - - private static final long serialVersionUID = 348147768L; - - public static final QProblemAdminId problemAdminId = new QProblemAdminId("problemAdminId"); - - public final StringPath id = createString("id"); - - public QProblemAdminId(String variable) { - super(ProblemAdminId.class, forVariable(variable)); - } - - public QProblemAdminId(Path<? extends ProblemAdminId> path) { - super(path.getType(), path.getMetadata()); - } - - public QProblemAdminId(PathMetadata metadata) { - super(ProblemAdminId.class, metadata); - } - -} - diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemCustomId.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemCustomId.java new file mode 100644 index 0000000..896d9cf --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemCustomId.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QProblemCustomId is a Querydsl query type for ProblemCustomId + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QProblemCustomId extends BeanPath<ProblemCustomId> { + + private static final long serialVersionUID = -517009730L; + + public static final QProblemCustomId problemCustomId = new QProblemCustomId("problemCustomId"); + + public final StringPath id = createString("id"); + + public QProblemCustomId(String variable) { + super(ProblemCustomId.class, forVariable(variable)); + } + + public QProblemCustomId(Path<? extends ProblemCustomId> path) { + super(path.getType(), path.getMetadata()); + } + + public QProblemCustomId(PathMetadata metadata) { + super(ProblemCustomId.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java index dc056d0..3909422 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java @@ -10,7 +10,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-12T02:55:03+0900", + date = "2025-02-18T17:01:34+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component @@ -31,7 +31,6 @@ public ChildProblem from(ChildProblemPostRequest request) { if ( set != null ) { childProblem.conceptTagIds( new LinkedHashSet<Long>( set ) ); } - childProblem.sequence( request.sequence() ); return childProblem.build(); } @@ -44,6 +43,7 @@ public ChildProblem from(ChildProblemUpdateRequest request) { ChildProblem.ChildProblemBuilder childProblem = ChildProblem.builder(); + childProblem.id( request.id() ); childProblem.imageUrl( request.imageUrl() ); childProblem.answerType( request.answerType() ); childProblem.answer( request.answer() ); @@ -51,7 +51,6 @@ public ChildProblem from(ChildProblemUpdateRequest request) { if ( set != null ) { childProblem.conceptTagIds( new LinkedHashSet<Long>( set ) ); } - childProblem.sequence( request.sequence() ); return childProblem.build(); } diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index dbb174c..2b75c34 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -2,7 +2,7 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import java.util.ArrayList; @@ -14,15 +14,15 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-13T19:05:39+0900", + date = "2025-02-18T15:29:27+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component public class ProblemMapperImpl implements ProblemMapper { @Override - public Problem from(ProblemPostRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag) { - if ( request == null && problemAdminId == null && practiceTestTag == null ) { + public Problem from(ProblemPostRequest request, ProblemCustomId problemCustomId, PracticeTestTag practiceTestTag) { + if ( request == null && problemCustomId == null && practiceTestTag == null ) { return null; } @@ -32,15 +32,15 @@ public Problem from(ProblemPostRequest request, ProblemAdminId problemAdminId, P problem.problemType( request.problemType() ); problem.number( request.number() ); } - problem.problemAdminId( problemAdminId ); + problem.problemCustomId( problemCustomId ); problem.practiceTestTag( practiceTestTag ); return problem.build(); } @Override - public Problem from(ProblemUpdateRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag) { - if ( request == null && problemAdminId == null && practiceTestTag == null ) { + public Problem from(ProblemUpdateRequest request, ProblemCustomId problemCustomId, PracticeTestTag practiceTestTag) { + if ( request == null && problemCustomId == null && practiceTestTag == null ) { return null; } @@ -68,7 +68,7 @@ public Problem from(ProblemUpdateRequest request, ProblemAdminId problemAdminId, problem.problemType( request.problemType() ); problem.number( request.number() ); } - problem.problemAdminId( problemAdminId ); + problem.problemCustomId( problemCustomId ); problem.practiceTestTag( practiceTestTag ); return problem.build(); diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java index b73be8c..9f3b995 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java @@ -37,7 +37,7 @@ public class QIncorrectProblem extends EntityPathBase<IncorrectProblem> { public final NumberPath<Long> practiceTestId = createNumber("practiceTestId", Long.class); - public final NumberPath<Long> problemId = createNumber("problemId", Long.class); + public final NumberPath<Long> problemId = createNumber("problemCustomId", Long.class); public final StringPath problemNumber = createString("problemNumber"); diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java index b7e7668..67b69aa 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java @@ -25,7 +25,7 @@ public class QProblemImageForTest extends EntityPathBase<ProblemImageForTest> { public final StringPath imageUrl = createString("imageUrl"); - public final NumberPath<Long> problemId = createNumber("problemId", Long.class); + public final NumberPath<Long> problemId = createNumber("problemCustomId", Long.class); public QProblemImageForTest(String variable) { super(ProblemImageForTest.class, forVariable(variable)); diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/AccessTokenResponse.java b/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/AccessTokenResponse.java index f8d70c2..25f5b8f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/AccessTokenResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/AccessTokenResponse.java @@ -1,5 +1,9 @@ package com.moplus.moplus_server.domain.auth.dto.response; +import jakarta.validation.constraints.NotNull; + public record AccessTokenResponse( - String accessToken -) {} \ No newline at end of file + @NotNull(message = "accessToken을 입력해주세요.") + String accessToken +) { +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java b/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java index e575025..9670ed7 100644 --- a/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java +++ b/src/main/java/com/moplus/moplus_server/domain/concept/controller/ConceptTagController.java @@ -17,7 +17,7 @@ @RequiredArgsConstructor public class ConceptTagController { - ConceptTagRepository conceptTagRepository; + private final ConceptTagRepository conceptTagRepository; @GetMapping("") @Operation(summary = "모든 개념 태그 리스트 조회") diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java index d4acee7..6002770 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java @@ -3,10 +3,12 @@ import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.problem.service.ChildProblemService; import com.moplus.moplus_server.domain.problem.service.ProblemDeleteService; import com.moplus.moplus_server.domain.problem.service.ProblemGetService; import com.moplus.moplus_server.domain.problem.service.ProblemSaveService; import com.moplus.moplus_server.domain.problem.service.ProblemUpdateService; +import com.moplus.moplus_server.global.response.IdResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -16,6 +18,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -30,6 +33,7 @@ public class ProblemController { private final ProblemUpdateService problemUpdateService; private final ProblemGetService problemGetService; private final ProblemDeleteService problemDeleteService; + private final ChildProblemService childProblemService; @GetMapping("/{id}") @Operation(summary = "문항 조회", description = "문항를 조회합니다.") @@ -40,15 +44,15 @@ public ResponseEntity<ProblemGetResponse> getProblem( } @PostMapping("") - @Operation(summary = "문항 생성", description = "문제를 생성합니다. 새끼 문항은 list 순서대로 sequence를 저장합니다.") - public ResponseEntity<Long> createProblem( + @Operation(summary = "문항 생성", description = "문제를 생성합니다. 기출/변형 문제는 모든 값이 필수이며 창작 문제는 문항 타입만 필수 입니다.") + public ResponseEntity<IdResponse> createProblem( @Valid @RequestBody ProblemPostRequest request ) { - return ResponseEntity.ok(problemSaveService.createProblem(request)); + return ResponseEntity.ok(new IdResponse(problemSaveService.createProblem(request))); } - @PostMapping("/{id}") - @Operation(summary = "문항 업데이트", description = "문제를 업데이트합니다. 문항 번호, 모의고사는 수정할 수 없습니다. 새로 추가되는 새끼문항 id는 빈 값입니다.") + @PutMapping("/{id}") + @Operation(summary = "문항 업데이트", description = "문제를 업데이트합니다. 새끼문항은 들어온 list의 순서로 저장됩니다.") public ResponseEntity<ProblemGetResponse> updateProblem( @PathVariable("id") Long id, @RequestBody ProblemUpdateRequest request @@ -64,4 +68,22 @@ public ResponseEntity<Void> updateProblem( problemDeleteService.deleteProblem(id); return ResponseEntity.ok().body(null); } + + @PostMapping("/{problemId}/child-problems") + @Operation(summary = "새끼문항 추가", description = "추가되는 새끼 문항의 id를 반환합니다. 컨펌 이후에는 새끼 문항 추가가 불가능합니다.") + public ResponseEntity<IdResponse> createChildProblem( + @PathVariable("problemId") Long problemId + ) { + return ResponseEntity.ok(new IdResponse(childProblemService.createChildProblem(problemId))); + } + + @DeleteMapping("/{problemId}/child-problems/{childProblemId}") + @Operation(summary = "새끼 문항 삭제", description = "컨펌 이후에는 새끼 문항 삭제가 불가능합니다.") + public ResponseEntity<Void> deleteChildProblem( + @PathVariable("problemId") Long problemId, + @PathVariable("childProblemId") Long childProblemId + ) { + childProblemService.deleteChildProblem(problemId, childProblemId); + return ResponseEntity.ok().body(null); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java index 7ec3490..fab72f2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java @@ -26,11 +26,12 @@ public class ProblemSearchController { description = "문항 ID, 문제명, 개념 태그리스트로 문제를 검색합니다. 개념 태그리스트는 OR 조건으로 검색하며 값이 없으면 쿼리파라미터에서 빼주세요" ) public ResponseEntity<List<ProblemSearchGetResponse>> search( - @RequestParam(value = "problemId", required = false) String problemId, + @RequestParam(value = "problemCustomId", required = false) String problemCustomId, @RequestParam(value = "comment", required = false) String comment, @RequestParam(value = "conceptTagIds", required = false) List<Long> conceptTagIds ) { - List<ProblemSearchGetResponse> problems = problemSearchRepository.search(problemId, comment, conceptTagIds); + List<ProblemSearchGetResponse> problems = problemSearchRepository.search(problemCustomId, comment, + conceptTagIds); return ResponseEntity.ok(problems); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java index f42c2d7..8396008 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java @@ -40,17 +40,24 @@ public class ChildProblem extends BaseEntity { private Answer answer; @Enumerated(EnumType.STRING) private AnswerType answerType; - private int sequence; @Builder - public ChildProblem(String imageUrl, AnswerType answerType, String answer, Set<Long> conceptTagIds, - int sequence) { + public ChildProblem(Long id, String imageUrl, AnswerType answerType, String answer, Set<Long> conceptTagIds) { + this.id = id; validateAnswerByType(answer, answerType); this.imageUrl = imageUrl; this.answerType = answerType; this.answer = new Answer(answer, answerType); this.conceptTagIds = conceptTagIds; - this.sequence = sequence; + } + + public static ChildProblem createEmptyChildProblem() { + return ChildProblem.builder() + .imageUrl("") + .answerType(AnswerType.SHORT_STRING_ANSWER) + .answer("") + .conceptTagIds(Set.of()) + .build(); } public void validateAnswerByType(String answer, AnswerType answerType) { @@ -62,11 +69,13 @@ public void validateAnswerByType(String answer, AnswerType answerType) { } public void update(ChildProblem input) { + if (this.id != input.id) { + throw new InvalidValueException(ErrorCode.INVALID_CHILD_PROBLEM_SEQUENCE); + } this.imageUrl = input.imageUrl; this.answerType = input.answerType; this.answer = input.answer; this.conceptTagIds = input.conceptTagIds; - this.sequence = input.sequence; } public String getAnswer() { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index bc3f96b..2b08fad 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -5,6 +5,8 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.repository.converter.StringListConverter; import com.moplus.moplus_server.global.common.BaseEntity; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; import jakarta.persistence.CascadeType; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; @@ -20,9 +22,8 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; -import jakarta.persistence.OrderBy; +import jakarta.persistence.OrderColumn; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -37,7 +38,7 @@ public class Problem extends BaseEntity { @Embedded - ProblemAdminId problemAdminId; + ProblemCustomId problemCustomId; Long practiceTestId; int number; @Enumerated(EnumType.STRING) @@ -76,7 +77,7 @@ public class Problem extends BaseEntity { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name = "problem_id") - @OrderBy("sequence ASC") + @OrderColumn(name = "sequence") private List<ChildProblem> childProblems = new ArrayList<>(); @Builder @@ -85,7 +86,7 @@ public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType List<String> prescriptionImageUrls, String seniorTipImageUrl, String readingTipImageUrl, String mainAnalysisImageUrl, String mainProblemImageUrl, String memo, String answer, String title, ProblemType problemType, int number, PracticeTestTag practiceTestTag, - ProblemAdminId problemAdminId) { + ProblemCustomId problemCustomId) { this.childProblems = childProblems; this.isConfirmed = isConfirmed; this.answerType = answerType; @@ -103,22 +104,15 @@ public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType this.problemType = problemType; this.number = number; this.practiceTestId = practiceTestTag.getId(); - this.problemAdminId = problemAdminId; + this.problemCustomId = problemCustomId; } public String getAnswer() { return answer.getValue(); } - public void addChildProblem(List<ChildProblem> inputChildProblems) { - List<ChildProblem> mutableChildProblems = new ArrayList<>(inputChildProblems); - mutableChildProblems.sort(Comparator.comparingInt(ChildProblem::getSequence)); - mutableChildProblems.forEach(childProblems::add); - mutableChildProblems.forEach(childProblem -> conceptTagIds.addAll(childProblem.getConceptTagIds())); - } - public void update(Problem inputProblem) { - this.problemAdminId = inputProblem.getProblemAdminId(); + this.problemCustomId = inputProblem.getProblemCustomId(); this.practiceTestId = inputProblem.getPracticeTestId(); this.number = inputProblem.getNumber(); this.problemType = inputProblem.getProblemType(); @@ -134,33 +128,20 @@ public void update(Problem inputProblem) { this.seniorTipImageUrl = inputProblem.getSeniorTipImageUrl(); this.prescriptionImageUrls = inputProblem.getPrescriptionImageUrls(); this.answerType = inputProblem.getAnswerType(); - this.isConfirmed = inputProblem.isConfirmed(); } public void updateChildProblem(List<ChildProblem> inputChildProblems) { - inputChildProblems.forEach(childProblem -> { - this.childProblems.stream() - .filter(existingChildProblem -> existingChildProblem.getId().equals(childProblem.getId())) - .findFirst() - .ifPresentOrElse( - existingChildProblem -> { - existingChildProblem.update(childProblem); - conceptTagIds.addAll(existingChildProblem.getConceptTagIds()); - }, - () -> { - childProblems.add(childProblem); - conceptTagIds.addAll(childProblem.getConceptTagIds()); - } - ); - }); - } - - public void deleteChildProblem(List<Long> deleteChildProblems) { - childProblems.removeIf(childProblem -> deleteChildProblems.contains(childProblem.getId())); + if (isConfirmed && this.childProblems.size() != inputChildProblems.size()) { + throw new InvalidValueException(ErrorCode.INVALID_CHILD_PROBLEM_SIZE); + } + + for (int i = 0; i < inputChildProblems.size(); i++) { + this.childProblems.get(i).update(inputChildProblems.get(i)); + } } public boolean isValid() { - return problemAdminId != null + return problemCustomId != null && practiceTestId != null && problemType != null && title != null && !title.getTitle().isEmpty() diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java index 8c61f9f..d447346 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java @@ -22,7 +22,7 @@ public class ProblemAdminIdService { NN : 번호 (01~99) XX : 2자리 sequence 숫자 */ - public ProblemAdminId nextId(int number, PracticeTestTag practiceTestTag, ProblemType problemType) { + public ProblemCustomId nextId(int number, PracticeTestTag practiceTestTag, ProblemType problemType) { int problemTypeCode = problemType.getCode(); // C (문제 타입) int subject = practiceTestTag.getSubject().getCode(); // S (과목) @@ -37,8 +37,8 @@ public ProblemAdminId nextId(int number, PracticeTestTag practiceTestTag, Proble sequence = SEQUENCE.getAndIncrement() % 100; // 000~999 순환 generatedId = String.format("%d%d%02d%02d%02d%02d", problemTypeCode, subject, year, month, number, sequence); - } while (problemRepository.existsByProblemAdminId(new ProblemAdminId(generatedId))); // ID가 이미 존재하면 재생성 + } while (problemRepository.existsByProblemCustomId(new ProblemCustomId(generatedId))); // ID가 이미 존재하면 재생성 - return new ProblemAdminId(generatedId); + return new ProblemCustomId(generatedId); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemCustomId.java similarity index 71% rename from src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java rename to src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemCustomId.java index 7c04c6e..85648be 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemCustomId.java @@ -10,12 +10,12 @@ @Getter @Embeddable @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ProblemAdminId implements Serializable { +public class ProblemCustomId implements Serializable { - @Column(name = "problem_admin_id", nullable = false) + @Column(name = "problem_custom_id") private String id; - public ProblemAdminId(String id) { + public ProblemCustomId(String id) { this.id = id; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java index 19ba8ff..c791a80 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java @@ -1,16 +1,13 @@ package com.moplus.moplus_server.domain.problem.dto.request; import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; -import io.swagger.v3.oas.annotations.media.Schema; import java.util.Set; public record ChildProblemUpdateRequest( - @Schema(description = "새로 생성되는 새끼문항은 빈 값입니다.") Long id, String imageUrl, AnswerType answerType, String answer, - Set<Long> conceptTagIds, - int sequence + Set<Long> conceptTagIds ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java index 0933dec..94f597f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java @@ -2,7 +2,7 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import jakarta.validation.constraints.NotNull; @@ -12,9 +12,9 @@ public record ProblemPostRequest( Long practiceTestId, int number ) { - public Problem toEntity(PracticeTestTag practiceTestTag, ProblemAdminId problemAdminId) { + public Problem toEntity(PracticeTestTag practiceTestTag, ProblemCustomId problemCustomId) { return Problem.builder() - .problemAdminId(problemAdminId) + .problemCustomId(problemCustomId) .practiceTestTag(practiceTestTag) .number(number) .title("") diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java index c55fb83..a4d0971 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java @@ -23,7 +23,6 @@ public record ProblemUpdateRequest( String seniorTipImageUrl, List<String> prescriptionImageUrls, AnswerType answerType, - List<ChildProblemUpdateRequest> updateChildProblems, - List<Long> deleteChildProblems + List<ChildProblemUpdateRequest> updateChildProblems ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java index 650189b..6354dda 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java @@ -10,8 +10,10 @@ @Builder public record ProblemGetResponse( - @NotNull(message = "문항 ID은 필수입니다") - String problemId, + @NotNull(message = "문항 ID 필수입니다") + Long id, + @NotNull(message = "문항 custom ID는 필수입니다") + String problemCustomId, Set<Long> conceptTagIds, Long practiceTestId, int number, @@ -31,8 +33,10 @@ public record ProblemGetResponse( ) { public static ProblemGetResponse of(Problem problem) { + return ProblemGetResponse.builder() - .problemId(problem.getProblemAdminId().getId()) + .id(problem.getId()) + .problemCustomId(problem.getProblemCustomId().getId()) .conceptTagIds(problem.getConceptTagIds()) .practiceTestId(problem.getPracticeTestId()) .number(problem.getNumber()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java index 6f62f77..4529f68 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java @@ -1,14 +1,14 @@ package com.moplus.moplus_server.domain.problem.repository; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; public interface ProblemRepository extends JpaRepository<Problem, Long> { - boolean existsByProblemAdminId(ProblemAdminId problemAdminId); + boolean existsByProblemCustomId(ProblemCustomId problemCustomId); default void existsByIdElseThrow(Long id) { if (!existsById(id)) { @@ -16,8 +16,8 @@ default void existsByIdElseThrow(Long id) { } } - default void existsByProblemAdminIdElseThrow(ProblemAdminId problemAdminId) { - if (!existsByProblemAdminId(problemAdminId)) { + default void existsByProblemAdminIdElseThrow(ProblemCustomId problemCustomId) { + if (!existsByProblemCustomId(problemCustomId)) { throw new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java index b2ff859..9c57fd1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java @@ -21,7 +21,7 @@ public class ProblemSearchRepositoryCustom { public List<ProblemSearchGetResponse> search(String problemId, String comment, List<Long> conceptTagIds) { return queryFactory - .select(problem.problemAdminId.id, problem.memo, problem.mainProblemImageUrl) + .select(problem.problemCustomId.id, problem.memo, problem.mainProblemImageUrl) .from(problem) .where( containsProblemId(problemId), @@ -32,7 +32,7 @@ public List<ProblemSearchGetResponse> search(String problemId, String comment, L .distinct() .transform(GroupBy.groupBy(problem.id).list( Projections.constructor(ProblemSearchGetResponse.class, - problem.problemAdminId.id, + problem.problemCustomId.id, problem.memo, problem.mainProblemImageUrl, GroupBy.set( @@ -45,10 +45,10 @@ public List<ProblemSearchGetResponse> search(String problemId, String comment, L )); } - //problemId 일부 포함 검색 + //problemCustomId 일부 포함 검색 private BooleanExpression containsProblemId(String problemId) { return (problemId == null || problemId.isEmpty()) ? null - : problem.problemAdminId.id.containsIgnoreCase(problemId); + : problem.problemCustomId.id.containsIgnoreCase(problemId); } //name 조건 (포함 검색) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ChildProblemService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ChildProblemService.java new file mode 100644 index 0000000..d4e7a43 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ChildProblemService.java @@ -0,0 +1,38 @@ +package com.moplus.moplus_server.domain.problem.service; + +import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.repository.ChildProblemRepository; +import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ChildProblemService { + + private final ProblemRepository problemRepository; + private final ChildProblemRepository childProblemRepository; + + @Transactional + public Long createChildProblem(Long problemId) { + Problem problem = problemRepository.findByIdElseThrow(problemId); + if (problem.isConfirmed()) { + throw new InvalidValueException(ErrorCode.CHILD_PROBLEM_UPDATE_AFTER_CONFIRMED); + } + + return childProblemRepository.save(ChildProblem.createEmptyChildProblem()).getId(); + } + + @Transactional + public void deleteChildProblem(Long problemId, Long childProblemId) { + Problem problem = problemRepository.findByIdElseThrow(problemId); + if (problem.isConfirmed()) { + throw new InvalidValueException(ErrorCode.CHILD_PROBLEM_UPDATE_AFTER_CONFIRMED); + } + childProblemRepository.deleteById(childProblemId); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java index 9617adf..6ba2888 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.problem.service; import com.amazonaws.HttpMethod; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemImageType; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.global.utils.s3.S3Util; @@ -18,7 +18,7 @@ public class ImageUploadService { private final ProblemRepository problemRepository; public String generateProblemImagePresignedUrl(String problemId, ProblemImageType imageType) { - problemRepository.existsByProblemAdminIdElseThrow(new ProblemAdminId(problemId)); + problemRepository.existsByProblemAdminIdElseThrow(new ProblemCustomId(problemId)); String fileName = generateProblemImageFileName(problemId, imageType); return s3Util.getS3PresignedUrl(fileName, HttpMethod.PUT); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java index a6f6679..24ea2f9 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java @@ -2,8 +2,8 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminIdService; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; @@ -24,10 +24,10 @@ public class ProblemSaveService { @Transactional public Long createProblem(ProblemPostRequest request) { PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); - ProblemAdminId problemAdminId = problemAdminIdService.nextId(request.number(), practiceTestTag, + ProblemCustomId problemCustomId = problemAdminIdService.nextId(request.number(), practiceTestTag, request.problemType()); - Problem problem = problemMapper.from(request, problemAdminId, practiceTestTag); + Problem problem = problemMapper.from(request, problemCustomId, practiceTestTag); return problemRepository.save(problem).getId(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java index 81f1086..423fd3b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java @@ -4,12 +4,10 @@ import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminIdService; -import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; -import com.moplus.moplus_server.domain.problem.repository.ChildProblemRepository; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problem.service.mapper.ChildProblemMapper; @@ -27,7 +25,6 @@ public class ProblemUpdateService { private final ProblemAdminIdService problemAdminIdService; private final PracticeTestTagRepository practiceTestRepository; private final ConceptTagRepository conceptTagRepository; - private final ChildProblemRepository childProblemRepository; private final ChildProblemMapper childProblemMapper; private final ProblemMapper problemMapper; @@ -37,12 +34,11 @@ public ProblemGetResponse updateProblem(Long problemId, ProblemUpdateRequest req conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); Problem problem = problemRepository.findByIdElseThrow(problemId); - ProblemAdminId problemAdminId = problemAdminIdService.nextId(request.number(), practiceTestTag, + ProblemCustomId problemCustomId = problemAdminIdService.nextId(request.number(), practiceTestTag, request.problemType()); - Problem inputProblem = problemMapper.from(request, problemAdminId, practiceTestTag); + Problem inputProblem = problemMapper.from(request, problemCustomId, practiceTestTag); problem.update(inputProblem); - problem.deleteChildProblem(request.deleteChildProblems()); List<ChildProblem> childProblems = changeToChildProblems(request); problem.updateChildProblem(childProblems); @@ -52,16 +48,7 @@ public ProblemGetResponse updateProblem(Long problemId, ProblemUpdateRequest req private List<ChildProblem> changeToChildProblems(ProblemUpdateRequest request) { return request.updateChildProblems().stream() - .map(this::getChildProblem) + .map(childProblemMapper::from) .toList(); } - - private ChildProblem getChildProblem(ChildProblemUpdateRequest updateChildProblem) { - if (updateChildProblem.id() == null) { - return childProblemMapper.from(updateChildProblem); - } - ChildProblem childProblem = childProblemRepository.findByIdElseThrow(updateChildProblem.id()); - childProblem.update(childProblemMapper.from(updateChildProblem)); - return childProblem; - } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java index d8a6566..e2deb5a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java @@ -4,11 +4,13 @@ import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; @Mapper(componentModel = "spring") public interface ChildProblemMapper { ChildProblem from(ChildProblemPostRequest request); + @Mapping(target = "id", source = "id") ChildProblem from(ChildProblemUpdateRequest request); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java index 5497c41..354ab5b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java @@ -2,7 +2,7 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import org.mapstruct.Mapper; @@ -13,14 +13,14 @@ public interface ProblemMapper { @Mappings({ - @Mapping(target = "problemAdminId", source = "problemAdminId"), + @Mapping(target = "problemCustomId", source = "problemCustomId"), @Mapping(target = "practiceTestTag", source = "practiceTestTag"), }) - Problem from(ProblemPostRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag); + Problem from(ProblemPostRequest request, ProblemCustomId problemCustomId, PracticeTestTag practiceTestTag); @Mappings({ - @Mapping(target = "problemAdminId", source = "problemAdminId"), + @Mapping(target = "problemCustomId", source = "problemCustomId"), @Mapping(target = "practiceTestTag", source = "practiceTestTag"), }) - Problem from(ProblemUpdateRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag); + Problem from(ProblemUpdateRequest request, ProblemCustomId problemCustomId, PracticeTestTag practiceTestTag); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java index f38a715..0b8296e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -9,6 +9,7 @@ import com.moplus.moplus_server.domain.problemset.service.ProblemSetGetService; import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService; import com.moplus.moplus_server.domain.problemset.service.ProblemSetUpdateService; +import com.moplus.moplus_server.global.response.IdResponse; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -36,10 +37,10 @@ public class ProblemSetController { @PostMapping("") @Operation(summary = "문항세트 생성", description = "문항세트를 생성합니다. 문항은 요청 순서대로 저장합니다.") - public ResponseEntity<Long> createProblemSet( + public ResponseEntity<IdResponse> createProblemSet( @RequestBody ProblemSetPostRequest request ) { - return ResponseEntity.ok(problemSetSaveService.createProblemSet(request)); + return ResponseEntity.ok(new IdResponse(problemSetSaveService.createProblemSet(request))); } @Hidden diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index 7a99cfd..ee5fa4f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -35,6 +35,7 @@ public class ProblemSet extends BaseEntity { @Embedded private Title title; + @Column(nullable = false) private boolean isDeleted; @@ -68,7 +69,7 @@ public void toggleConfirm(List<Problem> problems) { if (this.confirmStatus == ProblemSetConfirmStatus.NOT_CONFIRMED) { List<String> invalidProblemIds = problems.stream() .filter(problem -> !problem.isValid()) - .map(problem -> problem.getProblemAdminId().getId()) + .map(problem -> problem.getProblemCustomId().getId()) .toList(); if (!invalidProblemIds.isEmpty()) { String message = ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage() + diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java index 78ce956..4c28056 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -16,7 +16,7 @@ public record ProblemSummaryResponse( public static ProblemSummaryResponse of(Problem problem, String practiceTestName, List<String> tagNames) { return ProblemSummaryResponse.builder() - .problemId(problem.getProblemAdminId().getId()) + .problemId(problem.getProblemCustomId().getId()) .number(problem.getNumber()) .memo(problem.getMemo()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java index 0415f17..ddd8728 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java @@ -5,6 +5,7 @@ import com.moplus.moplus_server.domain.publish.service.PublishDeleteService; import com.moplus.moplus_server.domain.publish.service.PublishGetService; import com.moplus.moplus_server.domain.publish.service.PublishSaveService; +import com.moplus.moplus_server.global.response.IdResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -40,10 +41,10 @@ public ResponseEntity<List<PublishMonthGetResponse>> getPublishMonth( @PostMapping("") @Operation(summary = "발행 생성하기", description = "특정 날짜에 문항세트를 발행합니다.") - public ResponseEntity<Long> postPublish( + public ResponseEntity<IdResponse> postPublish( @Valid @RequestBody PublishPostRequest request ) { - return ResponseEntity.ok(publishSaveService.createPublish(request)); + return ResponseEntity.ok(new IdResponse(publishSaveService.createPublish(request))); } @DeleteMapping("/{publishId}") diff --git a/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java b/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java index 1663f45..784551f 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/WebConfig.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.global.annotation.AuthenticationArgumentResolver; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -17,13 +18,13 @@ public class WebConfig implements WebMvcConfigurer { private final AuthenticationArgumentResolver authenticationArgumentResolver; + @Value("${cors-allowed-origins}") + private List<String> corsAllowedOrigins; @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("https://dev.mopl.kr", "http://dev.mopl.kr", "http://localhost:8080", - "https://www.mopl.kr", "http" - + "://localhost:3000", "https://dev-web.mopl.kr", "http://dev-web.mopl.kr") + .allowedOrigins(corsAllowedOrigins.toArray(new String[0])) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 199a216..f3c83b2 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -37,6 +37,10 @@ public enum ErrorCode { //새끼 문항 CHILD_PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 새끼 문제를 찾을 수 없습니다"), + CHILD_PROBLEM_UPDATE_AFTER_CONFIRMED(HttpStatus.BAD_REQUEST, "컨펌 후 문제는 수정할 수 없습니다"), + INVALID_CHILD_PROBLEM_SEQUENCE(HttpStatus.BAD_REQUEST, "새끼 문제의 업데이트 순서가 일치하지 않습니다."), + INVALID_CHILD_PROBLEM_SIZE(HttpStatus.BAD_REQUEST, "새끼 문제의 업데이트 개수가 일치하지 않습니다."), + //개념태그 CONCEPT_TAG_NOT_FOUND_IN_LIST(HttpStatus.NOT_FOUND, "해당 리스트 중 존재하지 않는 개념 태그가 있습니다."), diff --git a/src/main/java/com/moplus/moplus_server/global/response/IdResponse.java b/src/main/java/com/moplus/moplus_server/global/response/IdResponse.java new file mode 100644 index 0000000..8c44006 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/response/IdResponse.java @@ -0,0 +1,6 @@ +package com.moplus.moplus_server.global.response; + +public record IdResponse( + Long id +) { +} diff --git a/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java b/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java deleted file mode 100644 index 545e075..0000000 --- a/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.moplus.moplus_server.global.response; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.moplus.moplus_server.global.error.ErrorResponse; -import org.springframework.http.HttpStatus; - -@JsonInclude(JsonInclude.Include.NON_NULL) -public record ResponseDto<T>( - T data, - String message, - HttpStatus status -) { - public static <T> ResponseDto<T> success(final T data) { - return new ResponseDto<>(data, null, null); - } - - public static <T> ResponseDto<T> fail(ErrorResponse errorResponse) { - return new ResponseDto<>(null, errorResponse.getMessage(), errorResponse.getStatus()); - } -} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java b/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java deleted file mode 100644 index af24122..0000000 --- a/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.moplus.moplus_server.global.response; - -import com.moplus.moplus_server.global.error.ErrorResponse; -import com.moplus.moplus_server.global.error.exception.ErrorCode; -import org.springframework.core.MethodParameter; -import org.springframework.http.MediaType; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; - -@RestControllerAdvice( - basePackages = "com.moplus.moplus_server" -) - -public class ResponseDtoAdvice implements ResponseBodyAdvice<Object> { - - @Override - public boolean supports(MethodParameter returnType, Class converterType) { - return !(returnType.getParameterType() == ResponseDto.class) - && MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType); - } - - @Override - public Object beforeBodyWrite( - Object body, - MethodParameter returnType, - MediaType selectedContentType, - Class selectedConverterType, - ServerHttpRequest request, - ServerHttpResponse response - ) { - if (body instanceof ErrorResponse) { - return ResponseDto.fail((ErrorResponse) body); - } - return ResponseDto.success(body); - } -} diff --git a/src/main/resources/templates/imageUploadPage.html b/src/main/resources/templates/imageUploadPage.html index 59fe602..d26dcd4 100644 --- a/src/main/resources/templates/imageUploadPage.html +++ b/src/main/resources/templates/imageUploadPage.html @@ -22,7 +22,7 @@ <h2>문제 이미지 업로드</h2> <!-- 반복문으로 문제 리스트 출력 --> <th:block th:each="problemForTest : ${problemImageRequests}"> <tr> - <td th:text="${problemForTest.problemAdminId}">문제 ID</td> + <td th:text="${problemForTest.problemCustomId}">문제 ID</td> <td th:text="${problemForTest.problemNumber}">문제 번호</td> <td> <!-- 이미지 URL이 있는 경우 이미지를 표시 --> @@ -34,7 +34,7 @@ <h2>문제 이미지 업로드</h2> <td> <!-- 업로드 버튼 --> <form enctype="multipart/form-data" method="post" - th:action="'/admin/practiceTests/uploadImage/' + ${problemForTest.problemAdminId}"> + th:action="'/admin/practiceTests/uploadImage/' + ${problemForTest.problemCustomId}"> <input accept="image/*" answerType="file" name="image"> <input answerType="hidden" name="practiceTestId" th:value="${practiceTestId}"> <button answerType="submit" class="upload-button">업로드</button> diff --git a/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java b/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java index 3d3622d..2ce90a3 100644 --- a/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java @@ -70,9 +70,9 @@ public void setMockMvc() throws Exception { .contentType("application/json") .header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)) .andExpect(status().isOk()) // 200 응답 확인 - .andExpect(jsonPath("$.data.id").exists()) // MemberGetResponse의 필드 확인 - .andExpect(jsonPath("$.data.name").exists()) - .andExpect(jsonPath("$.data.email").exists()); + .andExpect(jsonPath("$.id").exists()) // MemberGetResponse의 필드 확인 + .andExpect(jsonPath("$.name").exists()) + .andExpect(jsonPath("$.email").exists()); } } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemCustomIdServiceTest.java similarity index 83% rename from src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java rename to src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemCustomIdServiceTest.java index 184e71c..00c0f57 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemCustomIdServiceTest.java @@ -19,7 +19,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class ProblemAdminIdServiceTest { +class ProblemCustomIdServiceTest { public static final String ID_LENGTH = "10"; @Mock @@ -43,10 +43,10 @@ void setUp() { // given int 문제번호 = 20; ProblemType problemType = ProblemType.GICHUL_PROBLEM; - when(problemRepository.existsByProblemAdminId(any())).thenReturn(false); // 중복 없음 + when(problemRepository.existsByProblemCustomId(any())).thenReturn(false); // 중복 없음 // when - ProblemAdminId generatedId = problemAdminIdService.nextId(문제번호, practiceTestTag, problemType); + ProblemCustomId generatedId = problemAdminIdService.nextId(문제번호, practiceTestTag, problemType); // then assertThat(generatedId).isNotNull(); @@ -54,7 +54,7 @@ void setUp() { assertThat(generatedId.getId()).startsWith("12240520"); // 문제 ID 중복 확인을 위해 existsById 호출 확인 - verify(problemRepository, atLeastOnce()).existsByProblemAdminId(any()); + verify(problemRepository, atLeastOnce()).existsByProblemCustomId(any()); } @@ -63,12 +63,12 @@ void setUp() { // given int 문제번호 = 2; ProblemType problemType = ProblemType.GICHUL_PROBLEM; - when(problemRepository.existsByProblemAdminId(any())) + when(problemRepository.existsByProblemCustomId(any())) .thenReturn(true) // 첫 번째 생성된 ID는 중복됨 .thenReturn(false); // 두 번째는 중복 없음 // when - ProblemAdminId generatedId = problemAdminIdService.nextId(문제번호, practiceTestTag, problemType); + ProblemCustomId generatedId = problemAdminIdService.nextId(문제번호, practiceTestTag, problemType); // then assertThat(generatedId).isNotNull(); @@ -76,6 +76,6 @@ void setUp() { assertThat(generatedId.getId()).startsWith("12240502"); // 중복된 ID가 나왔으므로 existsById가 최소 두 번 이상 호출되었는지 확인 - verify(problemRepository, atLeast(2)).existsByProblemAdminId(any()); + verify(problemRepository, atLeast(2)).existsByProblemCustomId(any()); } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java index 6760214..9b9145d 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java @@ -6,7 +6,7 @@ import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; @@ -15,7 +15,6 @@ import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.util.List; import java.util.Set; -import java.util.stream.IntStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -37,35 +36,29 @@ class ProblemUpdateServiceTest { @Autowired private ProblemRepository problemRepository; - private ProblemAdminId problemAdminId; + private ProblemCustomId problemCustomId; private ProblemUpdateRequest problemUpdateRequest; @BeforeEach void setUp() { - problemAdminId = new ProblemAdminId("240520012001"); - - // 새 자식 문제 추가 - ChildProblemUpdateRequest newChildProblem = new ChildProblemUpdateRequest( - null, - "newChild.png", - AnswerType.SHORT_STRING_ANSWER, - "새로운 정답", - Set.of(1L, 2L), - 1 - ); + problemCustomId = new ProblemCustomId("240520012001"); // 기존 자식 문제 업데이트 - ChildProblemUpdateRequest updateChildProblem = new ChildProblemUpdateRequest( - 1L, // 기존 자식 문제 ID - "updatedChild.png", + ChildProblemUpdateRequest updateChildProblem1 = new ChildProblemUpdateRequest( + 1L, + "updatedChild1.png", AnswerType.MULTIPLE_CHOICE, "2", - Set.of(2L, 3L), - 0 + Set.of(2L, 3L) ); - // 기존 자식 문제 삭제 - List<Long> deleteChildProblem = List.of(2L); // 삭제할 자식 문제 ID + ChildProblemUpdateRequest updateChildProblem2 = new ChildProblemUpdateRequest( + 2L, + "updatedChild2.png", + AnswerType.SHORT_STRING_ANSWER, + "23", + Set.of(3L, 4L) + ); problemUpdateRequest = new ProblemUpdateRequest( ProblemType.VARIANT_PROBLEM, @@ -83,8 +76,7 @@ void setUp() { "updatedSeniorTip.png", List.of("prescription1.png", "prescription2.png"), // List<String>으로 변경 AnswerType.SHORT_STRING_ANSWER, - List.of(newChildProblem, updateChildProblem), - deleteChildProblem + List.of(updateChildProblem1, updateChildProblem2) ); } @@ -99,7 +91,7 @@ class 문제_업데이트_정상_동작 { // then assertThat(response).isNotNull(); - assertThat(response.problemId()).startsWith("22230310"); // 문제 ID 확인 + assertThat(response.problemCustomId()).startsWith("22230310"); // 문제 ID 확인 assertThat(response.problemType()).isEqualTo(ProblemType.VARIANT_PROBLEM); assertThat(response.practiceTestId()).isEqualTo(2L); assertThat(response.number()).isEqualTo(10); @@ -126,35 +118,21 @@ class 문제_업데이트_정상_동작 { // 자식 문제 검증 List<ChildProblem> childProblems = updatedProblem.getChildProblems(); - assertThat(childProblems).hasSize(2); // 기존 2개 → 1개 삭제, 1개 추가 후 2개 + assertThat(childProblems).hasSize(2); // 첫 번째 자식 문제 검증 (업데이트된 기존 문제) ChildProblem updatedChild = childProblems.get(0); - assertThat(updatedChild.getId()).isEqualTo(1L); - assertThat(updatedChild.getImageUrl()).isEqualTo("updatedChild.png"); + assertThat(updatedChild.getImageUrl()).isEqualTo("updatedChild1.png"); assertThat(updatedChild.getAnswerType()).isEqualTo(AnswerType.MULTIPLE_CHOICE); assertThat(updatedChild.getAnswer()).isEqualTo("2"); assertThat(updatedChild.getConceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(2L, 3L)); - assertThat(updatedChild.getSequence()).isEqualTo(0); // 두 번째 자식 문제 검증 (새로 추가된 문제) ChildProblem newChild = childProblems.get(1); - assertThat(newChild.getImageUrl()).isEqualTo("newChild.png"); + assertThat(newChild.getImageUrl()).isEqualTo("updatedChild2.png"); assertThat(newChild.getAnswerType()).isEqualTo(AnswerType.SHORT_STRING_ANSWER); - assertThat(newChild.getAnswer()).isEqualTo("새로운 정답"); - assertThat(newChild.getConceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(1L, 2L)); - assertThat(newChild.getSequence()).isEqualTo(1); - - // 부모 문제의 conceptTagIds가 자식 문제의 conceptTagIds를 모두 포함하는지 검증 - Set<Long> problemTags = updatedProblem.getConceptTagIds(); - childProblems.forEach(child -> { - assertThat(problemTags).containsAll(child.getConceptTagIds()); - }); - - // 자식 문제 순서가 올바르게 정렬되었는지 확인 - IntStream.range(0, childProblems.size()).forEach(i -> { - assertThat(childProblems.get(i).getSequence()).isEqualTo(i); - }); + assertThat(newChild.getAnswer()).isEqualTo("23"); + assertThat(newChild.getConceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(3L, 4L)); } } @@ -190,7 +168,6 @@ class 문제_업데이트_예외_처리 { "updatedSeniorTip.png", List.of("prescription1.png"), // List<String>으로 변경 AnswerType.SHORT_STRING_ANSWER, - List.of(), List.of() ); diff --git a/src/test/resources/insert-problem.sql b/src/test/resources/insert-problem.sql index add8767..03bc152 100644 --- a/src/test/resources/insert-problem.sql +++ b/src/test/resources/insert-problem.sql @@ -1,5 +1,5 @@ INSERT INTO problem (problem_id, - problem_admin_id, + problem_custom_id, practice_test_id, number, problem_type, @@ -28,7 +28,7 @@ INSERT INTO child_problem (child_problem_id, answer, sequence) VALUES (1, 1, 'child1.png', 'MULTIPLE_CHOICE', '1', 0), - (2, 1, 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 0); + (2, 1, 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 1); -- 문제-컨셉 태그 연결 (기존 문제의 ConceptTag) INSERT INTO problem_concept (problem_id, -- Long 타입으로 변경 (Problem의 id 참조) diff --git a/src/test/resources/insert-problem2.sql b/src/test/resources/insert-problem2.sql index b999009..75f6573 100644 --- a/src/test/resources/insert-problem2.sql +++ b/src/test/resources/insert-problem2.sql @@ -1,6 +1,6 @@ -- problem 데이터 삽입 INSERT INTO problem (problem_id, - problem_admin_id, + problem_custom_id, practice_test_id, number, problem_type, @@ -62,7 +62,7 @@ VALUES (1, 1), -- 유효하지 않은 문제 데이터 삽입 INSERT INTO problem (problem_id, - problem_admin_id, + problem_custom_id, practice_test_id, number, problem_type, From eb44526800d0961b95cccaf624cf4df5d08c10ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:32:47 +0900 Subject: [PATCH 087/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=ED=83=80=EC=9D=B4=ED=8B=80,=20=EB=82=9C=EC=9D=B4=EB=8F=84=20nu?= =?UTF-8?q?ll=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problem/controller/ProblemController.java | 5 +++-- .../domain/problem/domain/problem/Difficulty.java | 3 --- .../domain/problem/domain/problem/Problem.java | 6 +++--- .../problem/dto/response/ProblemPostResponse.java | 15 +++++++++++++++ .../problem/service/ProblemSaveService.java | 6 +++--- .../problem/service/ProblemSaveServiceTest.java | 7 ++++--- 6 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemPostResponse.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java index 6002770..9db45a7 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; +import com.moplus.moplus_server.domain.problem.dto.response.ProblemPostResponse; import com.moplus.moplus_server.domain.problem.service.ChildProblemService; import com.moplus.moplus_server.domain.problem.service.ProblemDeleteService; import com.moplus.moplus_server.domain.problem.service.ProblemGetService; @@ -45,10 +46,10 @@ public ResponseEntity<ProblemGetResponse> getProblem( @PostMapping("") @Operation(summary = "문항 생성", description = "문제를 생성합니다. 기출/변형 문제는 모든 값이 필수이며 창작 문제는 문항 타입만 필수 입니다.") - public ResponseEntity<IdResponse> createProblem( + public ResponseEntity<ProblemPostResponse> createProblem( @Valid @RequestBody ProblemPostRequest request ) { - return ResponseEntity.ok(new IdResponse(problemSaveService.createProblem(request))); + return ResponseEntity.ok(problemSaveService.createProblem(request)); } @PutMapping("/{id}") diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java index 7cbcf54..9617418 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java @@ -17,9 +17,6 @@ public class Difficulty { private Integer difficulty; public Difficulty(Integer difficulty) { - if (difficulty == null) { - return; - } validate(difficulty); this.difficulty = difficulty; } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index 2b08fad..e245855 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -134,7 +134,7 @@ public void updateChildProblem(List<ChildProblem> inputChildProblems) { if (isConfirmed && this.childProblems.size() != inputChildProblems.size()) { throw new InvalidValueException(ErrorCode.INVALID_CHILD_PROBLEM_SIZE); } - + for (int i = 0; i < inputChildProblems.size(); i++) { this.childProblems.get(i).update(inputChildProblems.get(i)); } @@ -160,10 +160,10 @@ public boolean isValid() { } public String getTitle() { - return title.getTitle(); + return title != null ? title.getTitle() : "제목 없음"; } public Integer getDifficulty() { - return difficulty.getDifficulty(); + return difficulty != null ? difficulty.getDifficulty() : null; } } \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemPostResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemPostResponse.java new file mode 100644 index 0000000..330c694 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemPostResponse.java @@ -0,0 +1,15 @@ +package com.moplus.moplus_server.domain.problem.dto.response; + +import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import jakarta.validation.constraints.NotNull; + +public record ProblemPostResponse( + @NotNull(message = "문항 ID는 필수입니다") + Long id, + @NotNull(message = "문항 custom ID는 필수입니다") + Long problemCustomId +) { + public static ProblemPostResponse of(Problem problem) { + return new ProblemPostResponse(problem.getId(), problem.getPracticeTestId()); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java index 24ea2f9..7ceacf4 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java @@ -5,6 +5,7 @@ import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminIdService; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; +import com.moplus.moplus_server.domain.problem.dto.response.ProblemPostResponse; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problem.service.mapper.ProblemMapper; @@ -22,13 +23,12 @@ public class ProblemSaveService { private final ProblemMapper problemMapper; @Transactional - public Long createProblem(ProblemPostRequest request) { + public ProblemPostResponse createProblem(ProblemPostRequest request) { PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); ProblemCustomId problemCustomId = problemAdminIdService.nextId(request.number(), practiceTestTag, request.problemType()); Problem problem = problemMapper.from(request, problemCustomId, practiceTestTag); - - return problemRepository.save(problem).getId(); + return ProblemPostResponse.of(problemRepository.save(problem)); } } diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java index 21b2a2f..e6d5ae2 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java @@ -6,6 +6,7 @@ import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; +import com.moplus.moplus_server.domain.problem.dto.response.ProblemPostResponse; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.junit.jupiter.api.BeforeEach; @@ -46,12 +47,12 @@ class 문항생성 { @Test void 성공() { // when - Long createdProblemId = problemSaveService.createProblem(problemPostRequest); + ProblemPostResponse problemResponse = problemSaveService.createProblem(problemPostRequest); // then - assertThat(createdProblemId).isNotNull(); + assertThat(problemResponse).isNotNull(); - Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemId); + Problem savedProblem = problemRepository.findByIdElseThrow(problemResponse.id()); assertThat(savedProblem).isNotNull(); assertThat(savedProblem.getProblemType()).isEqualTo(ProblemType.GICHUL_PROBLEM); } From 7f8454c6922695f6503578dbd9a1b5efedcd7f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:34:41 +0900 Subject: [PATCH 088/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=ED=83=80=EC=9D=B4=ED=8B=80,=20=EB=82=9C=EC=9D=B4=EB=8F=84=20nu?= =?UTF-8?q?ll=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problem/domain/problem/Difficulty.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java index 9617418..68a0949 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java @@ -21,7 +21,10 @@ public Difficulty(Integer difficulty) { this.difficulty = difficulty; } - private void validate(int difficulty) { + private void validate(Integer difficulty) { + if (difficulty == null) { + return; + } if (difficulty < 1 || difficulty > 10) { throw new InvalidValueException(ErrorCode.INVALID_DIFFICULTY); } From c7adf26072c55d8246c35c1c0d59d5b5cfebab27 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Tue, 18 Feb 2025 20:02:17 +0900 Subject: [PATCH 089/115] =?UTF-8?q?[feat/#57]=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=EC=8A=A4?= =?UTF-8?q?=ED=82=A4=EB=A7=88=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/PresignedUrlResponse.java | 2 +- .../global/config/swagger/SwaggerConfig.java | 34 +++++++++++++++++ .../global/response/ResponseDto.java | 20 ++++++++++ .../global/response/ResponseDtoAdvice.java | 38 +++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java create mode 100644 src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java index 64b1011..2496345 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.problem.dto.response; public record PresignedUrlResponse( - String data + String presignedUrl ) { public static PresignedUrlResponse of(String presignedUrl) { return new PresignedUrlResponse( diff --git a/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java b/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java index 63543a2..32b9062 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java @@ -3,15 +3,20 @@ import com.moplus.moplus_server.global.properties.swagger.SwaggerProperties; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.Schema; 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 java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.springdoc.core.customizers.OperationCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; @Configuration @RequiredArgsConstructor @@ -43,4 +48,33 @@ public OpenAPI openAPI() { .version("v0.0.1")) .servers(addServerUrl()); } + + @Bean + public OperationCustomizer operationCustomizer() { + return (operation, handlerMethod) -> { + this.addResponseBodyWrapperSchemaExample(operation); + return operation; + }; + } + + private void addResponseBodyWrapperSchemaExample(Operation operation) { + final Content content = operation.getResponses().get("200").getContent(); + if (content != null) { + content.forEach((mediaTypeKey, mediaType) -> { + Schema<?> originalSchema = mediaType.getSchema(); + Schema<?> wrappedSchema = wrapSchema(originalSchema); + mediaType.setSchema(wrappedSchema); + }); + } + } + + private Schema<?> wrapSchema(Schema<?> originalSchema) { + final Schema<?> wrapperSchema = new Schema<>(); + + wrapperSchema.addProperty("data", originalSchema); + wrapperSchema.addProperty("message", new Schema<>().type("string").example("오류 메세지")); + wrapperSchema.addProperty("status", new Schema<>().type("string").example(HttpStatus.NOT_FOUND.name())); + + return wrapperSchema; + } } diff --git a/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java b/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java new file mode 100644 index 0000000..545e075 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java @@ -0,0 +1,20 @@ +package com.moplus.moplus_server.global.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.moplus.moplus_server.global.error.ErrorResponse; +import org.springframework.http.HttpStatus; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record ResponseDto<T>( + T data, + String message, + HttpStatus status +) { + public static <T> ResponseDto<T> success(final T data) { + return new ResponseDto<>(data, null, null); + } + + public static <T> ResponseDto<T> fail(ErrorResponse errorResponse) { + return new ResponseDto<>(null, errorResponse.getMessage(), errorResponse.getStatus()); + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java b/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java new file mode 100644 index 0000000..dc406c0 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java @@ -0,0 +1,38 @@ +package com.moplus.moplus_server.global.response; + +import com.moplus.moplus_server.global.error.ErrorResponse; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +@RestControllerAdvice( + basePackages = "com.moplus.moplus_server" +) + +public class ResponseDtoAdvice implements ResponseBodyAdvice<Object> { + + @Override + public boolean supports(MethodParameter returnType, Class converterType) { + return !(returnType.getParameterType() == ResponseDto.class) + && MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType); + } + + @Override + public Object beforeBodyWrite( + Object body, + MethodParameter returnType, + MediaType selectedContentType, + Class selectedConverterType, + ServerHttpRequest request, + ServerHttpResponse response + ) { + if (body instanceof ErrorResponse) { + return ResponseDto.fail((ErrorResponse) body); + } + return ResponseDto.success(body); + } +} \ No newline at end of file From ed18106e7934a32dfa60895556685590f52483ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 18 Feb 2025 20:02:54 +0900 Subject: [PATCH 090/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/mapper/ProblemMapperImpl.java | 17 ++++++++++++++++- .../domain/problem/domain/problem/Problem.java | 9 +++++++-- .../domain/problem/ProblemAdminIdService.java | 17 +++++++++++++++++ .../dto/response/ProblemGetResponse.java | 2 +- .../dto/response/ProblemPostResponse.java | 4 ++-- .../problem/service/ProblemSaveService.java | 17 +++++++++++++---- .../problem/service/mapper/ProblemMapper.java | 6 ++++++ .../domain/problemset/domain/ProblemSet.java | 2 +- .../dto/response/ProblemSummaryResponse.java | 2 +- 9 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index 2b75c34..ab47b56 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import java.util.ArrayList; @@ -14,7 +15,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-18T15:29:27+0900", + date = "2025-02-18T19:54:34+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component @@ -73,4 +74,18 @@ public Problem from(ProblemUpdateRequest request, ProblemCustomId problemCustomI return problem.build(); } + + @Override + public Problem from(ProblemType problemType, ProblemCustomId problemCustomId) { + if ( problemType == null && problemCustomId == null ) { + return null; + } + + Problem.ProblemBuilder problem = Problem.builder(); + + problem.problemType( problemType ); + problem.problemCustomId( problemCustomId ); + + return problem.build(); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index e245855..9ca4b79 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -103,7 +103,7 @@ public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType this.title = new Title(title); this.problemType = problemType; this.number = number; - this.practiceTestId = practiceTestTag.getId(); + this.practiceTestId = practiceTestTag != null ? practiceTestTag.getId() : null; this.problemCustomId = problemCustomId; } @@ -112,7 +112,7 @@ public String getAnswer() { } public void update(Problem inputProblem) { - this.problemCustomId = inputProblem.getProblemCustomId(); + this.problemCustomId = new ProblemCustomId(inputProblem.getProblemCustomId()); this.practiceTestId = inputProblem.getPracticeTestId(); this.number = inputProblem.getNumber(); this.problemType = inputProblem.getProblemType(); @@ -166,4 +166,9 @@ public String getTitle() { public Integer getDifficulty() { return difficulty != null ? difficulty.getDifficulty() : null; } + + public String getProblemCustomId() { + return problemCustomId.getId(); + } + } \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java index d447346..d779579 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java @@ -41,4 +41,21 @@ public ProblemCustomId nextId(int number, PracticeTestTag practiceTestTag, Probl return new ProblemCustomId(generatedId); } + + public ProblemCustomId nextId(ProblemType problemType) { + + int problemTypeCode = problemType.getCode(); // C (문제 타입) + + String generatedId; + int sequence; + + // 중복되지 않는 ID 찾을 때까지 반복 + do { + sequence = SEQUENCE.getAndIncrement() % 100; // 000~999 순환 + generatedId = String.format("%d%09d", + problemTypeCode, sequence); + } while (problemRepository.existsByProblemCustomId(new ProblemCustomId(generatedId))); // ID가 이미 존재하면 재생성 + + return new ProblemCustomId(generatedId); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java index 6354dda..eadc866 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java @@ -36,7 +36,7 @@ public static ProblemGetResponse of(Problem problem) { return ProblemGetResponse.builder() .id(problem.getId()) - .problemCustomId(problem.getProblemCustomId().getId()) + .problemCustomId(problem.getProblemCustomId()) .conceptTagIds(problem.getConceptTagIds()) .practiceTestId(problem.getPracticeTestId()) .number(problem.getNumber()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemPostResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemPostResponse.java index 330c694..5715c5c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemPostResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemPostResponse.java @@ -7,9 +7,9 @@ public record ProblemPostResponse( @NotNull(message = "문항 ID는 필수입니다") Long id, @NotNull(message = "문항 custom ID는 필수입니다") - Long problemCustomId + String problemCustomId ) { public static ProblemPostResponse of(Problem problem) { - return new ProblemPostResponse(problem.getId(), problem.getPracticeTestId()); + return new ProblemPostResponse(problem.getId(), problem.getProblemCustomId()); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java index 7ceacf4..668233a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java @@ -4,6 +4,7 @@ import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminIdService; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemPostResponse; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; @@ -24,11 +25,19 @@ public class ProblemSaveService { @Transactional public ProblemPostResponse createProblem(ProblemPostRequest request) { - PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); - ProblemCustomId problemCustomId = problemAdminIdService.nextId(request.number(), practiceTestTag, - request.problemType()); + Problem problem; + if (request.problemType() != ProblemType.CREATION_PROBLEM) { + PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); + ProblemCustomId problemCustomId = problemAdminIdService.nextId(request.number(), practiceTestTag, + request.problemType()); + + problem = problemMapper.from(request, problemCustomId, practiceTestTag); + } else { + ProblemCustomId problemCustomId = problemAdminIdService.nextId(request.problemType()); + problem = problemMapper.from(request.problemType(), problemCustomId); + } - Problem problem = problemMapper.from(request, problemCustomId, practiceTestTag); return ProblemPostResponse.of(problemRepository.save(problem)); } + } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java index 354ab5b..0ff784c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import org.mapstruct.Mapper; @@ -23,4 +24,9 @@ public interface ProblemMapper { @Mapping(target = "practiceTestTag", source = "practiceTestTag"), }) Problem from(ProblemUpdateRequest request, ProblemCustomId problemCustomId, PracticeTestTag practiceTestTag); + + @Mappings({ + @Mapping(target = "problemCustomId", source = "problemCustomId") + }) + Problem from(ProblemType problemType, ProblemCustomId problemCustomId); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index ee5fa4f..8d339ad 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -69,7 +69,7 @@ public void toggleConfirm(List<Problem> problems) { if (this.confirmStatus == ProblemSetConfirmStatus.NOT_CONFIRMED) { List<String> invalidProblemIds = problems.stream() .filter(problem -> !problem.isValid()) - .map(problem -> problem.getProblemCustomId().getId()) + .map(Problem::getProblemCustomId) .toList(); if (!invalidProblemIds.isEmpty()) { String message = ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage() + diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java index 4c28056..8317e89 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -16,7 +16,7 @@ public record ProblemSummaryResponse( public static ProblemSummaryResponse of(Problem problem, String practiceTestName, List<String> tagNames) { return ProblemSummaryResponse.builder() - .problemId(problem.getProblemCustomId().getId()) + .problemId(problem.getProblemCustomId()) .number(problem.getNumber()) .memo(problem.getMemo()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) From 13598354e891f7a6045d22278aca15f76166ab54 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Tue, 18 Feb 2025 21:05:50 +0900 Subject: [PATCH 091/115] =?UTF-8?q?[fix/#57]=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20json=20path=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberControllerTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java b/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java index 2ce90a3..3d3622d 100644 --- a/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/member/controller/MemberControllerTest.java @@ -70,9 +70,9 @@ public void setMockMvc() throws Exception { .contentType("application/json") .header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)) .andExpect(status().isOk()) // 200 응답 확인 - .andExpect(jsonPath("$.id").exists()) // MemberGetResponse의 필드 확인 - .andExpect(jsonPath("$.name").exists()) - .andExpect(jsonPath("$.email").exists()); + .andExpect(jsonPath("$.data.id").exists()) // MemberGetResponse의 필드 확인 + .andExpect(jsonPath("$.data.name").exists()) + .andExpect(jsonPath("$.data.email").exists()); } } } \ No newline at end of file From 95f20f3b145af0780d44cc9f2f1ef451e9477095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:43:04 +0900 Subject: [PATCH 092/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EC=8B=9C=EA=B0=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problem/domain/problem/QProblem.java | 3 ++ .../domain/problem/QRecommendedTime.java | 39 ++++++++++++++ .../service/mapper/ProblemMapperImpl.java | 4 +- .../controller/ProblemSearchController.java | 4 +- .../problem/domain/problem/Problem.java | 15 ++++-- .../domain/problem/RecommendedTime.java | 34 +++++++++++++ .../dto/request/ProblemUpdateRequest.java | 4 +- .../dto/response/ProblemGetResponse.java | 6 ++- .../ProblemSearchRepositoryCustom.java | 4 +- .../problem/service/mapper/ProblemMapper.java | 2 + .../problem/ProblemCustomIdServiceTest.java | 2 + .../service/ProblemUpdateServiceTest.java | 20 +++++--- src/test/resources/concept-tag.sql | 2 + src/test/resources/insert-problem.sql | 51 +++++++++++++++---- src/test/resources/insert-problem2.sql | 25 ++++++--- src/test/resources/practice-test-tag.sql | 2 + 16 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QRecommendedTime.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/RecommendedTime.java diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java index 4850572..c06ac88 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java @@ -61,6 +61,8 @@ public class QProblem extends EntityPathBase<Problem> { public final StringPath readingTipImageUrl = createString("readingTipImageUrl"); + public final QRecommendedTime recommendedTime; + public final StringPath seniorTipImageUrl = createString("seniorTipImageUrl"); public final QTitle title; @@ -89,6 +91,7 @@ public QProblem(Class<? extends Problem> type, PathMetadata metadata, PathInits this.answer = inits.isInitialized("answer") ? new com.moplus.moplus_server.domain.problem.domain.QAnswer(forProperty("answer")) : null; this.difficulty = inits.isInitialized("difficulty") ? new QDifficulty(forProperty("difficulty")) : null; this.problemCustomId = inits.isInitialized("problemCustomId") ? new QProblemCustomId(forProperty("problemCustomId")) : null; + this.recommendedTime = inits.isInitialized("recommendedTime") ? new QRecommendedTime(forProperty("recommendedTime")) : null; this.title = inits.isInitialized("title") ? new QTitle(forProperty("title")) : null; } diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QRecommendedTime.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QRecommendedTime.java new file mode 100644 index 0000000..2ef9140 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QRecommendedTime.java @@ -0,0 +1,39 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QRecommendedTime is a Querydsl query type for RecommendedTime + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QRecommendedTime extends BeanPath<RecommendedTime> { + + private static final long serialVersionUID = -1102611877L; + + public static final QRecommendedTime recommendedTime = new QRecommendedTime("recommendedTime"); + + public final NumberPath<Integer> minute = createNumber("minute", Integer.class); + + public final NumberPath<Integer> second = createNumber("second", Integer.class); + + public QRecommendedTime(String variable) { + super(RecommendedTime.class, forVariable(variable)); + } + + public QRecommendedTime(Path<? extends RecommendedTime> path) { + super(path.getType(), path.getMetadata()); + } + + public QRecommendedTime(PathMetadata metadata) { + super(RecommendedTime.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index ab47b56..f3bf9ff 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -15,7 +15,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-18T19:54:34+0900", + date = "2025-02-18T21:41:47+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component @@ -48,6 +48,8 @@ public Problem from(ProblemUpdateRequest request, ProblemCustomId problemCustomI Problem.ProblemBuilder problem = Problem.builder(); if ( request != null ) { + problem.recommendedMinute( request.recommendedMinute() ); + problem.recommendedSecond( request.recommendedSecond() ); problem.answerType( request.answerType() ); Set<Long> set = request.conceptTagIds(); if ( set != null ) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java index fab72f2..7deed3b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java @@ -27,10 +27,10 @@ public class ProblemSearchController { ) public ResponseEntity<List<ProblemSearchGetResponse>> search( @RequestParam(value = "problemCustomId", required = false) String problemCustomId, - @RequestParam(value = "comment", required = false) String comment, + @RequestParam(value = "memo", required = false) String memo, @RequestParam(value = "conceptTagIds", required = false) List<Long> conceptTagIds ) { - List<ProblemSearchGetResponse> problems = problemSearchRepository.search(problemCustomId, comment, + List<ProblemSearchGetResponse> problems = problemSearchRepository.search(problemCustomId, memo, conceptTagIds); return ResponseEntity.ok(problems); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index 9ca4b79..f4257c2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -80,13 +80,16 @@ public class Problem extends BaseEntity { @OrderColumn(name = "sequence") private List<ChildProblem> childProblems = new ArrayList<>(); + @Embedded + private RecommendedTime recommendedTime; + @Builder public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType answerType, Set<Long> conceptTagIds, Integer difficulty, String mainHandwritingExplanationImageUrl, List<String> prescriptionImageUrls, String seniorTipImageUrl, String readingTipImageUrl, String mainAnalysisImageUrl, String mainProblemImageUrl, String memo, String answer, String title, ProblemType problemType, int number, PracticeTestTag practiceTestTag, - ProblemCustomId problemCustomId) { + ProblemCustomId problemCustomId, Integer recommendedMinute, Integer recommendedSecond) { this.childProblems = childProblems; this.isConfirmed = isConfirmed; this.answerType = answerType; @@ -105,6 +108,7 @@ public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType this.number = number; this.practiceTestId = practiceTestTag != null ? practiceTestTag.getId() : null; this.problemCustomId = problemCustomId; + this.recommendedTime = new RecommendedTime(recommendedMinute, recommendedSecond); } public String getAnswer() { @@ -123,11 +127,15 @@ public void update(Problem inputProblem) { this.memo = inputProblem.getMemo(); this.mainProblemImageUrl = inputProblem.getMainProblemImageUrl(); this.mainAnalysisImageUrl = inputProblem.getMainAnalysisImageUrl(); - this.mainHandwritingExplanationImageUrl = inputProblem.getMainHandwritingExplanationImageUrl(); // 추가 + this.mainHandwritingExplanationImageUrl = inputProblem.getMainHandwritingExplanationImageUrl(); this.readingTipImageUrl = inputProblem.getReadingTipImageUrl(); this.seniorTipImageUrl = inputProblem.getSeniorTipImageUrl(); this.prescriptionImageUrls = inputProblem.getPrescriptionImageUrls(); this.answerType = inputProblem.getAnswerType(); + this.recommendedTime = new RecommendedTime( + inputProblem.getRecommendedTime() != null ? inputProblem.getRecommendedTime().getMinute() : null, + inputProblem.getRecommendedTime() != null ? inputProblem.getRecommendedTime().getSecond() : null + ); } public void updateChildProblem(List<ChildProblem> inputChildProblems) { @@ -156,7 +164,8 @@ public boolean isValid() { && prescriptionImageUrls != null && !prescriptionImageUrls.isEmpty() && prescriptionImageUrls.stream().allMatch(url -> url != null && !url.isEmpty()) && answerType != null - && conceptTagIds != null && !conceptTagIds.isEmpty(); + && conceptTagIds != null && !conceptTagIds.isEmpty() + && recommendedTime != null; } public String getTitle() { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/RecommendedTime.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/RecommendedTime.java new file mode 100644 index 0000000..8c0b8f7 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/RecommendedTime.java @@ -0,0 +1,34 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RecommendedTime { + @Column(name = "recommended_minute") + private Integer minute; + @Column(name = "recommended_second") + private Integer second; + + public RecommendedTime(Integer minute, Integer second) { + validateTime(minute, second); + this.minute = minute; + this.second = second; + } + + private void validateTime(Integer minute, Integer second) { + if (minute != null && (minute < 0 || minute > 60)) { + throw new InvalidValueException(ErrorCode.INVALID_INPUT_VALUE); + } + if (second != null && (second < 0 || second > 60)) { + throw new InvalidValueException(ErrorCode.INVALID_INPUT_VALUE); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java index a4d0971..1603133 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java @@ -23,6 +23,8 @@ public record ProblemUpdateRequest( String seniorTipImageUrl, List<String> prescriptionImageUrls, AnswerType answerType, - List<ChildProblemUpdateRequest> updateChildProblems + List<ChildProblemUpdateRequest> updateChildProblems, + Integer recommendedMinute, + Integer recommendedSecond ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java index eadc866..2d4fd4b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java @@ -29,7 +29,9 @@ public record ProblemGetResponse( String readingTipImageUrl, String seniorTipImageUrl, List<String> prescriptionImageUrls, - List<ChildProblemGetResponse> childProblems + List<ChildProblemGetResponse> childProblems, + Integer recommendedMinute, + Integer recommendedSecond ) { public static ProblemGetResponse of(Problem problem) { @@ -55,6 +57,8 @@ public static ProblemGetResponse of(Problem problem) { .childProblems(problem.getChildProblems().stream() .map(ChildProblemGetResponse::of) .toList()) + .recommendedMinute(problem.getRecommendedTime().getMinute()) + .recommendedSecond(problem.getRecommendedTime().getSecond()) .build(); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java index 9c57fd1..bec194e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java @@ -19,13 +19,13 @@ public class ProblemSearchRepositoryCustom { private final JPAQueryFactory queryFactory; - public List<ProblemSearchGetResponse> search(String problemId, String comment, List<Long> conceptTagIds) { + public List<ProblemSearchGetResponse> search(String problemId, String memo, List<Long> conceptTagIds) { return queryFactory .select(problem.problemCustomId.id, problem.memo, problem.mainProblemImageUrl) .from(problem) .where( containsProblemId(problemId), - containsName(comment), + containsName(memo), inConceptTagIds(conceptTagIds) ) .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)).fetchJoin() diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java index 0ff784c..2f67b1d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java @@ -22,6 +22,8 @@ public interface ProblemMapper { @Mappings({ @Mapping(target = "problemCustomId", source = "problemCustomId"), @Mapping(target = "practiceTestTag", source = "practiceTestTag"), + @Mapping(target = "recommendedMinute", source = "request.recommendedMinute"), + @Mapping(target = "recommendedSecond", source = "request.recommendedSecond") }) Problem from(ProblemUpdateRequest request, ProblemCustomId problemCustomId, PracticeTestTag practiceTestTag); diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemCustomIdServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemCustomIdServiceTest.java index 00c0f57..c55b27b 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemCustomIdServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemCustomIdServiceTest.java @@ -17,7 +17,9 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.transaction.annotation.Transactional; +@Transactional @ExtendWith(MockitoExtension.class) class ProblemCustomIdServiceTest { diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java index 9b9145d..a9d2679 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java @@ -71,12 +71,14 @@ void setUp() { "업데이트된 메모", "updatedMainProblem.png", "updatedMainAnalysis.png", - "updatedMainHandwriting.png", // 추가 + "updatedMainHandwriting.png", "updatedReadingTip.png", "updatedSeniorTip.png", - List.of("prescription1.png", "prescription2.png"), // List<String>으로 변경 + List.of("prescription1.png", "prescription2.png"), AnswerType.SHORT_STRING_ANSWER, - List.of(updateChildProblem1, updateChildProblem2) + List.of(updateChildProblem1, updateChildProblem2), + 30, + 45 ); } @@ -133,6 +135,10 @@ class 문제_업데이트_정상_동작 { assertThat(newChild.getAnswerType()).isEqualTo(AnswerType.SHORT_STRING_ANSWER); assertThat(newChild.getAnswer()).isEqualTo("23"); assertThat(newChild.getConceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(3L, 4L)); + + // 추가된 검증 + assertThat(response.recommendedMinute()).isEqualTo(30); + assertThat(response.recommendedSecond()).isEqualTo(45); } } @@ -163,12 +169,14 @@ class 문제_업데이트_예외_처리 { "잘못된 메모", "updatedMainProblem.png", "updatedMainAnalysis.png", - "updatedMainHandwriting.png", // 추가 + "updatedMainHandwriting.png", "updatedReadingTip.png", "updatedSeniorTip.png", - List.of("prescription1.png"), // List<String>으로 변경 + List.of("prescription1.png"), AnswerType.SHORT_STRING_ANSWER, - List.of() + List.of(), + 30, + 45 ); // when & then diff --git a/src/test/resources/concept-tag.sql b/src/test/resources/concept-tag.sql index 1d06a9a..3485a8b 100644 --- a/src/test/resources/concept-tag.sql +++ b/src/test/resources/concept-tag.sql @@ -1,3 +1,5 @@ +DELETE FROM concept_tag; + INSERT INTO concept_tag (concept_tag_id, name) VALUES (1, '미분 개념'), (2, '적분 개념'), diff --git a/src/test/resources/insert-problem.sql b/src/test/resources/insert-problem.sql index 03bc152..0e6a2d4 100644 --- a/src/test/resources/insert-problem.sql +++ b/src/test/resources/insert-problem.sql @@ -1,3 +1,9 @@ +DELETE FROM child_problem_concept; +DELETE FROM problem_concept; +DELETE FROM child_problem; +DELETE FROM problem; + +-- 데이터 삽입 INSERT INTO problem (problem_id, problem_custom_id, practice_test_id, @@ -13,16 +19,29 @@ INSERT INTO problem (problem_id, senior_tip_image_url, prescription_image_urls, answer_type, - is_confirmed) + is_confirmed, + recommended_minute, + recommended_second) VALUES (1, '1224052001', 1, 1, 'GICHUL_PROBLEM', '제목1', '1', 5, '기존 문제 설명 1', 'mainProblem.png1', 'mainAnalysis.png1', 'readingTip.png1', 'seniorTip.png1', - 'prescription.png1', 'MULTIPLE_CHOICE', false), + 'prescription.png1', 'MULTIPLE_CHOICE', false, 30, 45), (2, '1224052002', 1, 1, 'GICHUL_PROBLEM', '제목2', '1', 5, '기존 문제 설명 2', 'mainProblem.png2', 'mainAnalysis.png2', 'readingTip.png2', 'seniorTip.png2', - 'prescription.png2', 'MULTIPLE_CHOICE', false); + 'prescription.png2', 'MULTIPLE_CHOICE', false, 25, 30); + +-- 자식 문제 테이블 생성 +CREATE TABLE IF NOT EXISTS child_problem ( + child_problem_id BIGINT PRIMARY KEY, + problem_id BIGINT, + image_url VARCHAR(255), + answer_type VARCHAR(50), + answer VARCHAR(255), + sequence INT +); +-- 자식 문제 데이터 삽입 INSERT INTO child_problem (child_problem_id, - problem_id, -- Long 타입으로 변경 (부모 Problem의 id 참조) + problem_id, image_url, answer_type, answer, @@ -30,18 +49,30 @@ INSERT INTO child_problem (child_problem_id, VALUES (1, 1, 'child1.png', 'MULTIPLE_CHOICE', '1', 0), (2, 1, 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 1); --- 문제-컨셉 태그 연결 (기존 문제의 ConceptTag) -INSERT INTO problem_concept (problem_id, -- Long 타입으로 변경 (Problem의 id 참조) - concept_tag_id) +-- 문제-컨셉 태그 연결 테이블 생성 +CREATE TABLE IF NOT EXISTS problem_concept ( + problem_id BIGINT, + concept_tag_id BIGINT, + PRIMARY KEY (problem_id, concept_tag_id) +); + +-- 문제-컨셉 태그 데이터 삽입 +INSERT INTO problem_concept (problem_id, concept_tag_id) VALUES (1, 1), (1, 2), (1, 3), (2, 1), (2, 3); --- 자식 문제-컨셉 태그 연결 -INSERT INTO child_problem_concept (child_problem_id, - concept_tag_id) +-- 자식 문제-컨셉 태그 연결 테이블 생성 +CREATE TABLE IF NOT EXISTS child_problem_concept ( + child_problem_id BIGINT, + concept_tag_id BIGINT, + PRIMARY KEY (child_problem_id, concept_tag_id) +); + +-- 자식 문제-컨셉 태그 데이터 삽입 +INSERT INTO child_problem_concept (child_problem_id, concept_tag_id) VALUES (1, 3), (1, 4), (2, 5), diff --git a/src/test/resources/insert-problem2.sql b/src/test/resources/insert-problem2.sql index 75f6573..ee909a8 100644 --- a/src/test/resources/insert-problem2.sql +++ b/src/test/resources/insert-problem2.sql @@ -1,3 +1,8 @@ +DELETE FROM child_problem_concept; +DELETE FROM problem_concept; +DELETE FROM child_problem; +DELETE FROM problem; + -- problem 데이터 삽입 INSERT INTO problem (problem_id, problem_custom_id, @@ -15,18 +20,23 @@ INSERT INTO problem (problem_id, senior_tip_image_url, prescription_image_urls, answer_type, - is_confirmed) + is_confirmed, + recommended_minute, + recommended_second) VALUES (1, '24052001001', 1, 1, 'GICHUL_PROBLEM', '제목1', '1', 5, '기존 문제 설명', 'mainProblem.png', 'mainAnalysis.png', 'mainHandwriting1.png', 'readingTip.png', 'seniorTip.png', - 'prescription1.png, prescription2.png', 'MULTIPLE_CHOICE', false), + 'prescription1.png, prescription2.png', 'MULTIPLE_CHOICE', false, + 30, 0), (2, '24052001002', 1, 2, 'GICHUL_PROBLEM', '제목2', '2', 4, '문제 2 설명', 'mainProblem2.png', 'mainAnalysis2.png', 'mainHandwriting2.png', 'readingTip2.png', 'seniorTip2.png', - 'prescription3.png, prescription4.png', 'MULTIPLE_CHOICE', false), + 'prescription3.png, prescription4.png', 'MULTIPLE_CHOICE', false, + 20, 30), (3, '24052001003', 1, 3, 'GICHUL_PROBLEM', '제목3', '3', 3, '문제 3 설명', 'mainProblem3.png', 'mainAnalysis3.png', 'mainHandwriting3.png', 'readingTip3.png', 'seniorTip3.png', - 'prescription5.png, prescription6.png', 'SHORT_STRING_ANSWER', true); + 'prescription5.png, prescription6.png', 'SHORT_STRING_ANSWER', true, + 15, 45); -- 자식 문제 데이터 삽입 INSERT INTO child_problem (child_problem_id, @@ -77,7 +87,10 @@ INSERT INTO problem (problem_id, senior_tip_image_url, prescription_image_urls, answer_type, - is_confirmed) + is_confirmed, + recommended_minute, + recommended_second) VALUES (4, '24052001004', 1, 4, 'GICHUL_PROBLEM', '', '', 1, '유효하지 않은 문제 설명', '', 'mainAnalysis4.png', '', 'readingTip4.png', 'seniorTip4.png', - '', 'MULTIPLE_CHOICE', false); \ No newline at end of file + '', 'MULTIPLE_CHOICE', false, + null, null); \ No newline at end of file diff --git a/src/test/resources/practice-test-tag.sql b/src/test/resources/practice-test-tag.sql index b1f5b17..60c45c7 100644 --- a/src/test/resources/practice-test-tag.sql +++ b/src/test/resources/practice-test-tag.sql @@ -1,3 +1,5 @@ +DELETE FROM practice_test_tag; + INSERT INTO practice_test_tag (practice_test_tag_id, name, test_year, test_month, subject, area) VALUES (1, '2025년 5월 고2 모의고사', 2024, 5, '고2', '수학'), (2, '2023년 3월 고2 모의고사', 2023, 3, '고2', '수학'); \ No newline at end of file From d6f6246bd21ccfabb2000bec24bf38e30db4c140 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Wed, 19 Feb 2025 16:38:29 +0900 Subject: [PATCH 093/115] =?UTF-8?q?[hotfix]=20=EA=B3=B5=ED=86=B5=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/swagger/SwaggerConfig.java | 5 +---- .../global/error/ErrorResponse.java | 4 +++- .../global/response/FailResponseDto.java | 16 +++++++++++++++ .../global/response/ResponseDto.java | 20 ------------------- .../global/response/ResponseDtoAdvice.java | 6 +++--- .../global/response/SuccessResponseDto.java | 12 +++++++++++ 6 files changed, 35 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/global/response/FailResponseDto.java delete mode 100644 src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java create mode 100644 src/main/java/com/moplus/moplus_server/global/response/SuccessResponseDto.java diff --git a/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java b/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java index 32b9062..15a9e50 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/swagger/SwaggerConfig.java @@ -70,11 +70,8 @@ private void addResponseBodyWrapperSchemaExample(Operation operation) { private Schema<?> wrapSchema(Schema<?> originalSchema) { final Schema<?> wrapperSchema = new Schema<>(); - wrapperSchema.addProperty("data", originalSchema); - wrapperSchema.addProperty("message", new Schema<>().type("string").example("오류 메세지")); - wrapperSchema.addProperty("status", new Schema<>().type("string").example(HttpStatus.NOT_FOUND.name())); - + wrapperSchema.setRequired(List.of("data")); return wrapperSchema; } } diff --git a/src/main/java/com/moplus/moplus_server/global/error/ErrorResponse.java b/src/main/java/com/moplus/moplus_server/global/error/ErrorResponse.java index 367f86f..76bae16 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/ErrorResponse.java +++ b/src/main/java/com/moplus/moplus_server/global/error/ErrorResponse.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.global.error; import com.moplus.moplus_server.global.error.exception.ErrorCode; +import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -9,8 +10,9 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ErrorResponse { - + @NotNull private String message; + @NotNull private HttpStatus status; private ErrorResponse(final ErrorCode code) { diff --git a/src/main/java/com/moplus/moplus_server/global/response/FailResponseDto.java b/src/main/java/com/moplus/moplus_server/global/response/FailResponseDto.java new file mode 100644 index 0000000..438897f --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/response/FailResponseDto.java @@ -0,0 +1,16 @@ +package com.moplus.moplus_server.global.response; + +import com.moplus.moplus_server.global.error.ErrorResponse; +import jakarta.validation.constraints.NotNull; +import org.springframework.http.HttpStatus; + +public record FailResponseDto( + @NotNull + String message, + @NotNull + HttpStatus status +) { + public static FailResponseDto fail(ErrorResponse errorResponse) { + return new FailResponseDto(errorResponse.getMessage(), errorResponse.getStatus()); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java b/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java deleted file mode 100644 index 545e075..0000000 --- a/src/main/java/com/moplus/moplus_server/global/response/ResponseDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.moplus.moplus_server.global.response; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.moplus.moplus_server.global.error.ErrorResponse; -import org.springframework.http.HttpStatus; - -@JsonInclude(JsonInclude.Include.NON_NULL) -public record ResponseDto<T>( - T data, - String message, - HttpStatus status -) { - public static <T> ResponseDto<T> success(final T data) { - return new ResponseDto<>(data, null, null); - } - - public static <T> ResponseDto<T> fail(ErrorResponse errorResponse) { - return new ResponseDto<>(null, errorResponse.getMessage(), errorResponse.getStatus()); - } -} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java b/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java index dc406c0..3e9dcfb 100644 --- a/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java +++ b/src/main/java/com/moplus/moplus_server/global/response/ResponseDtoAdvice.java @@ -17,7 +17,7 @@ public class ResponseDtoAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class converterType) { - return !(returnType.getParameterType() == ResponseDto.class) + return !(returnType.getParameterType() == SuccessResponseDto.class) && MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType); } @@ -31,8 +31,8 @@ public Object beforeBodyWrite( ServerHttpResponse response ) { if (body instanceof ErrorResponse) { - return ResponseDto.fail((ErrorResponse) body); + return FailResponseDto.fail((ErrorResponse) body); } - return ResponseDto.success(body); + return SuccessResponseDto.success(body); } } \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/global/response/SuccessResponseDto.java b/src/main/java/com/moplus/moplus_server/global/response/SuccessResponseDto.java new file mode 100644 index 0000000..4b21e03 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/response/SuccessResponseDto.java @@ -0,0 +1,12 @@ +package com.moplus.moplus_server.global.response; + +import jakarta.validation.constraints.NotNull; + +public record SuccessResponseDto<T>( + @NotNull + T data +) { + public static <T> SuccessResponseDto<T> success(final T data) { + return new SuccessResponseDto<>(data); + } +} \ No newline at end of file From 5337cbb816c7ebed64ca001e92af254e5f76263f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:53:03 +0900 Subject: [PATCH 094/115] =?UTF-8?q?[fix/#54]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/security/handler/EmailPasswordSuccessHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java index 67db246..403e8ea 100644 --- a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java +++ b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java @@ -31,12 +31,15 @@ public void onAuthenticationSuccess(final HttpServletRequest request, final Http response.addCookie(cookieUtil.createCookie(refreshToken)); + Map<String, Map<String, String>> commonResponse = new HashMap<>(); Map<String, String> tokenResponse = new HashMap<>(); tokenResponse.put("accessToken", accessToken); + commonResponse.put("data", tokenResponse); + response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); - response.getWriter().write(objectMapper.writeValueAsString(tokenResponse)); + response.getWriter().write(objectMapper.writeValueAsString(commonResponse)); } } From 0d0931044b28e0e0894e91d5ef76a8beee49269c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 20 Feb 2025 17:03:56 +0900 Subject: [PATCH 095/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/ChildProblemMapperImpl.java | 2 +- .../service/mapper/ProblemMapperImpl.java | 2 +- .../TestResult/entity/QIncorrectProblem.java | 2 +- .../domain/QProblemImageForTest.java | 2 +- .../controller/ImageUploadController.java | 2 +- .../domain/problem/domain/Answer.java | 2 +- .../domain/childProblem/ChildProblem.java | 2 +- .../problem/domain/problem/AnswerType.java | 27 +-------------- .../dto/request/ChildProblemPostRequest.java | 2 ++ .../request/ChildProblemUpdateRequest.java | 2 ++ .../dto/request/ProblemUpdateRequest.java | 2 ++ .../dto/response/ChildProblemGetResponse.java | 1 + .../dto/response/ProblemGetResponse.java | 3 ++ .../response/ProblemSearchGetResponse.java | 9 +++-- .../ProblemSearchRepositoryCustom.java | 33 +++++++++++++++++-- .../problem/service/ImageUploadService.java | 9 +++-- .../dto/request/ProblemReorderRequest.java | 2 ++ .../dto/request/ProblemSetPostRequest.java | 2 ++ .../dto/request/ProblemSetUpdateRequest.java | 2 ++ .../dto/response/ProblemSetGetResponse.java | 5 ++- .../response/ProblemSetSearchGetResponse.java | 5 ++- .../dto/response/ProblemSummaryResponse.java | 2 ++ .../ProblemSearchRepositoryCustomTest.java | 8 ++--- 23 files changed, 79 insertions(+), 49 deletions(-) diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java index 3909422..bc5fe7e 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java @@ -10,7 +10,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-18T17:01:34+0900", + date = "2025-02-20T02:36:18+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index f3bf9ff..428b7de 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -15,7 +15,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-18T21:41:47+0900", + date = "2025-02-20T02:36:19+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java index 9f3b995..b73be8c 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java @@ -37,7 +37,7 @@ public class QIncorrectProblem extends EntityPathBase<IncorrectProblem> { public final NumberPath<Long> practiceTestId = createNumber("practiceTestId", Long.class); - public final NumberPath<Long> problemId = createNumber("problemCustomId", Long.class); + public final NumberPath<Long> problemId = createNumber("problemId", Long.class); public final StringPath problemNumber = createString("problemNumber"); diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java index 67b69aa..b7e7668 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java @@ -25,7 +25,7 @@ public class QProblemImageForTest extends EntityPathBase<ProblemImageForTest> { public final StringPath imageUrl = createString("imageUrl"); - public final NumberPath<Long> problemId = createNumber("problemCustomId", Long.class); + public final NumberPath<Long> problemId = createNumber("problemId", Long.class); public QProblemImageForTest(String variable) { super(ProblemImageForTest.class, forVariable(variable)); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java index 89bdc0f..9afcfc1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ImageUploadController.java @@ -24,7 +24,7 @@ public class ImageUploadController { @Operation(summary = "이미지 업로드를 위한 presigned URL 발급") @GetMapping("/problem/{problemId}/presigned-url") public ResponseEntity<PresignedUrlResponse> getProblemImagePresignedUrl( - @PathVariable("problemId") String problemId, + @PathVariable("problemId") Long problemId, @RequestParam(value = "image-type") ProblemImageType imageType) { String presignedUrl = imageUploadService.generateProblemImagePresignedUrl(problemId, imageType); return ResponseEntity.ok(PresignedUrlResponse.of(presignedUrl)); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java index 79ec350..ae8764c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java @@ -31,7 +31,7 @@ private void validateByType(String answer, AnswerType answerType) { throw new InvalidValueException(ErrorCode.INVALID_MULTIPLE_CHOICE_ANSWER); } } - if (answerType == AnswerType.SHORT_NUMBER_ANSWER) { + if (answerType == AnswerType.SHORT_ANSWER) { try { int numericAnswer = Integer.parseInt(answer); if (numericAnswer < 0 || numericAnswer > 999) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java index 8396008..aff64c1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java @@ -54,7 +54,7 @@ public ChildProblem(Long id, String imageUrl, AnswerType answerType, String answ public static ChildProblem createEmptyChildProblem() { return ChildProblem.builder() .imageUrl("") - .answerType(AnswerType.SHORT_STRING_ANSWER) + .answerType(AnswerType.SHORT_ANSWER) .answer("") .conceptTagIds(Set.of()) .build(); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java index 27d748d..ac2760e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java @@ -5,33 +5,8 @@ @RequiredArgsConstructor public enum AnswerType { MULTIPLE_CHOICE("객관식"), - SHORT_NUMBER_ANSWER("주관식_숫자"), - SHORT_STRING_ANSWER("주관식_문자"); + SHORT_ANSWER("주관식"); private final String name; - - public static AnswerType getTypeForProblem(String subject, int number) { - - // 미적분, 기하, 확률과 통계 - if (subject.equals("미적분") || subject.equals("기하") || subject.equals("확률과통계")) { - if ((number >= 1 && number <= 15) || (number >= 23 && number <= 28)) { - return MULTIPLE_CHOICE; - } else if ((number >= 16 && number <= 22) || number == 29 || number == 30) { - return SHORT_NUMBER_ANSWER; - } - } - - // 고1, 고2 - if (subject.equals("고1") || subject.equals("고2")) { - if (number >= 1 && number <= 21) { - return MULTIPLE_CHOICE; - } else if (number >= 22 && number <= 30) { - return SHORT_NUMBER_ANSWER; - } - } - - // 기본값: 객관식 - return MULTIPLE_CHOICE; - } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java index fc3e302..0877351 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java @@ -1,12 +1,14 @@ package com.moplus.moplus_server.domain.problem.dto.request; import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; +import jakarta.validation.constraints.NotNull; import java.util.Set; public record ChildProblemPostRequest( String imageUrl, AnswerType answerType, String answer, + @NotNull(message = "컬렉션 값은 필수입니다.") Set<Long> conceptTagIds, int sequence ) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java index c791a80..a89a81c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.domain.problem.dto.request; import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; +import jakarta.validation.constraints.NotNull; import java.util.Set; public record ChildProblemUpdateRequest( @@ -8,6 +9,7 @@ public record ChildProblemUpdateRequest( String imageUrl, AnswerType answerType, String answer, + @NotNull(message = "컬렉션 값은 필수입니다.") Set<Long> conceptTagIds ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java index 1603133..a5aa604 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java @@ -11,6 +11,7 @@ public record ProblemUpdateRequest( ProblemType problemType, Long practiceTestId, int number, + @NotNull(message = "컬렉션 값은 필수입니다.") Set<Long> conceptTagIds, String answer, String title, @@ -23,6 +24,7 @@ public record ProblemUpdateRequest( String seniorTipImageUrl, List<String> prescriptionImageUrls, AnswerType answerType, + @NotNull(message = "컬렉션 값은 필수입니다.") List<ChildProblemUpdateRequest> updateChildProblems, Integer recommendedMinute, Integer recommendedSecond diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java index 3daed3d..b5fc52e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java @@ -13,6 +13,7 @@ public record ChildProblemGetResponse( String imageUrl, AnswerType answerType, String answer, + @NotNull(message = "컬렉션 값은 필수입니다.") Set<Long> conceptTagIds ) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java index 2d4fd4b..426f097 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java @@ -14,6 +14,7 @@ public record ProblemGetResponse( Long id, @NotNull(message = "문항 custom ID는 필수입니다") String problemCustomId, + @NotNull(message = "컬렉션 값은 필수입니다.") Set<Long> conceptTagIds, Long practiceTestId, int number, @@ -28,7 +29,9 @@ public record ProblemGetResponse( String mainAnalysisImageUrl, String readingTipImageUrl, String seniorTipImageUrl, + @NotNull(message = "컬렉션 값은 필수입니다.") List<String> prescriptionImageUrls, + @NotNull(message = "컬렉션 값은 필수입니다.") List<ChildProblemGetResponse> childProblems, Integer recommendedMinute, Integer recommendedSecond diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java index 459b45e..246bb54 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java @@ -8,15 +8,18 @@ @Getter @NoArgsConstructor public class ProblemSearchGetResponse { + private Long id; @NotNull(message = "문항 ID는 필수입니다") - private String problemId; + private String problemCustomId; private String memo; private String mainProblemImageUrl; + @NotNull(message = "개념 태그리스트는 필수입니다") private Set<ConceptTagSearchResponse> conceptTagResponses; - public ProblemSearchGetResponse(String problemId, String memo, String mainProblemImageUrl, + public ProblemSearchGetResponse(Long id, String problemCustomId, String memo, String mainProblemImageUrl, Set<ConceptTagSearchResponse> conceptTagResponses) { - this.problemId = problemId; + this.id = id; + this.problemCustomId = problemCustomId; this.memo = memo; this.mainProblemImageUrl = mainProblemImageUrl; this.conceptTagResponses = conceptTagResponses; diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java index bec194e..3457bce 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.domain.problem.repository; import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag; +import static com.moplus.moplus_server.domain.problem.domain.childProblem.QChildProblem.childProblem; import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; import com.moplus.moplus_server.domain.problem.dto.response.ConceptTagSearchResponse; @@ -8,6 +9,7 @@ import com.querydsl.core.group.GroupBy; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; import lombok.RequiredArgsConstructor; @@ -23,15 +25,18 @@ public List<ProblemSearchGetResponse> search(String problemId, String memo, List return queryFactory .select(problem.problemCustomId.id, problem.memo, problem.mainProblemImageUrl) .from(problem) + .leftJoin(childProblem).on(childProblem.in(problem.childProblems)) + .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds) + .or(conceptTag.id.in(childProblem.conceptTagIds))) .where( containsProblemId(problemId), containsName(memo), - inConceptTagIds(conceptTagIds) + hasConceptTags(conceptTagIds) ) - .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)).fetchJoin() .distinct() .transform(GroupBy.groupBy(problem.id).list( Projections.constructor(ProblemSearchGetResponse.class, + problem.id, problem.problemCustomId.id, problem.memo, problem.mainProblemImageUrl, @@ -45,6 +50,30 @@ public List<ProblemSearchGetResponse> search(String problemId, String memo, List )); } + private BooleanExpression hasConceptTags(List<Long> conceptTagIds) { + if (conceptTagIds == null || conceptTagIds.isEmpty()) { + return null; + } + + return problem.id.in( + JPAExpressions + .selectFrom(problem) + .where( + problem.conceptTagIds.any().in(conceptTagIds) + .or( + problem.id.in( + JPAExpressions + .select(childProblem.id) + .from(childProblem) + .where(childProblem.conceptTagIds.any() + .in(conceptTagIds)) + ) + ) + ) + .select(problem.id) + ); + } + //problemCustomId 일부 포함 검색 private BooleanExpression containsProblemId(String problemId) { return (problemId == null || problemId.isEmpty()) ? null diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java index 6ba2888..634f602 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ImageUploadService.java @@ -1,7 +1,6 @@ package com.moplus.moplus_server.domain.problem.service; import com.amazonaws.HttpMethod; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemCustomId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemImageType; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.global.utils.s3.S3Util; @@ -17,15 +16,15 @@ public class ImageUploadService { private final S3Util s3Util; private final ProblemRepository problemRepository; - public String generateProblemImagePresignedUrl(String problemId, ProblemImageType imageType) { - problemRepository.existsByProblemAdminIdElseThrow(new ProblemCustomId(problemId)); + public String generateProblemImagePresignedUrl(Long problemId, ProblemImageType imageType) { + problemRepository.existsByIdElseThrow(problemId); String fileName = generateProblemImageFileName(problemId, imageType); return s3Util.getS3PresignedUrl(fileName, HttpMethod.PUT); } - private String generateProblemImageFileName(String problemId, ProblemImageType imageType) { + private String generateProblemImageFileName(Long problemId, ProblemImageType imageType) { String uuid = UUID.randomUUID().toString(); - return String.format("%s%s/%s/%s.jpg", + return String.format("%s%d/%s/%s.jpg", PROBLEM_IMAGE_PREFIX, problemId, imageType.getType(), diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java index e398789..bd4e0fa 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java @@ -1,8 +1,10 @@ package com.moplus.moplus_server.domain.problemset.dto.request; +import jakarta.validation.constraints.NotNull; import java.util.List; public record ProblemReorderRequest( + @NotNull(message = "컬렉션 값은 필수입니다.") List<Long> newProblems ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java index d8d1abf..4b8a872 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java @@ -1,10 +1,12 @@ package com.moplus.moplus_server.domain.problemset.dto.request; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import jakarta.validation.constraints.NotNull; import java.util.List; public record ProblemSetPostRequest( String problemSetTitle, + @NotNull(message = "컬렉션 값은 필수입니다.") List<Long> problems ) { public ProblemSet toEntity(List<Long> problemIdList) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java index 3cd3091..97d6d48 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java @@ -1,9 +1,11 @@ package com.moplus.moplus_server.domain.problemset.dto.request; +import jakarta.validation.constraints.NotNull; import java.util.List; public record ProblemSetUpdateRequest( String problemSetTitle, + @NotNull(message = "컬렉션 값은 필수입니다.") List<Long> problemIds ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java index 7c32ba7..ab71eae 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java @@ -2,6 +2,7 @@ import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; +import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.util.List; import lombok.Builder; @@ -12,9 +13,11 @@ public record ProblemSetGetResponse( String title, ProblemSetConfirmStatus confirmStatus, LocalDate publishedDate, + @NotNull(message = "컬렉션 값은 필수입니다.") List<ProblemSummaryResponse> problemSummaries ) { - public static ProblemSetGetResponse of(ProblemSet problemSet, LocalDate publishedDate, List<ProblemSummaryResponse> problemSummaries) { + public static ProblemSetGetResponse of(ProblemSet problemSet, LocalDate publishedDate, + List<ProblemSummaryResponse> problemSummaries) { return ProblemSetGetResponse.builder() .id(problemSet.getId()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java index 789b310..5803ad1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.domain.problemset.dto.response; import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; +import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.util.List; import lombok.Getter; @@ -12,10 +13,12 @@ public class ProblemSetSearchGetResponse { private String problemSetTitle; private ProblemSetConfirmStatus confirmStatus; private LocalDate publishedDate; + @NotNull(message = "컬렉션 값은 필수입니다.") private List<ProblemThumbnailResponse> problemThumbnailResponses; public ProblemSetSearchGetResponse( - String problemSetTitle, ProblemSetConfirmStatus confirmStatus, LocalDate publishedDate, List<ProblemThumbnailResponse> problemThumbnailResponses + String problemSetTitle, ProblemSetConfirmStatus confirmStatus, LocalDate publishedDate, + List<ProblemThumbnailResponse> problemThumbnailResponses ) { this.problemSetTitle = problemSetTitle; this.confirmStatus = confirmStatus; diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java index 8317e89..4bb2cfe 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.domain.problemset.dto.response; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import jakarta.validation.constraints.NotNull; import java.util.List; import lombok.Builder; @@ -11,6 +12,7 @@ public record ProblemSummaryResponse( String practiceTestName, String memo, String mainProblemImageUrl, + @NotNull(message = "컬렉션 값은 필수입니다.") List<String> tagNames ) { public static ProblemSummaryResponse of(Problem problem, String practiceTestName, List<String> tagNames) { diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java index 4eb1e25..d586582 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java @@ -27,7 +27,7 @@ public class ProblemSearchRepositoryCustomTest { // then assertThat(result).hasSize(2); - assertThat(result).extracting(ProblemSearchGetResponse::getProblemId) + assertThat(result).extracting(ProblemSearchGetResponse::getProblemCustomId) .containsExactlyInAnyOrder("1224052001", "1224052002"); } @@ -38,7 +38,7 @@ public class ProblemSearchRepositoryCustomTest { // then assertThat(result).hasSize(1); - assertThat(result.get(0).getProblemId()).isEqualTo("1224052001"); + assertThat(result.get(0).getProblemCustomId()).isEqualTo("1224052001"); } @Test @@ -48,7 +48,7 @@ public class ProblemSearchRepositoryCustomTest { // then assertThat(result).hasSize(2); - assertThat(result).extracting(ProblemSearchGetResponse::getProblemId) + assertThat(result).extracting(ProblemSearchGetResponse::getProblemCustomId) .containsExactlyInAnyOrder("1224052001", "1224052002"); } @@ -59,7 +59,7 @@ public class ProblemSearchRepositoryCustomTest { // then assertThat(result).hasSize(1); - assertThat(result.get(0).getProblemId()).isEqualTo("1224052001"); + assertThat(result.get(0).getProblemCustomId()).isEqualTo("1224052001"); } @Test From 78ffa70f826791e9b30da91db67851f12636d11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 20 Feb 2025 17:18:49 +0900 Subject: [PATCH 096/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problem/service/mapper/ChildProblemMapperImpl.java | 2 +- .../domain/problem/dto/request/ChildProblemUpdateRequest.java | 2 +- .../domain/problem/dto/response/PresignedUrlResponse.java | 3 +++ .../domain/problem/dto/response/ProblemSearchGetResponse.java | 3 ++- .../com/moplus/moplus_server/global/response/IdResponse.java | 3 +++ 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java index bc5fe7e..e5b5da2 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java @@ -43,7 +43,7 @@ public ChildProblem from(ChildProblemUpdateRequest request) { ChildProblem.ChildProblemBuilder childProblem = ChildProblem.builder(); - childProblem.id( request.id() ); + childProblem.id( request.childProblemId() ); childProblem.imageUrl( request.imageUrl() ); childProblem.answerType( request.answerType() ); childProblem.answer( request.answer() ); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java index a89a81c..ec78dd1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java @@ -5,7 +5,7 @@ import java.util.Set; public record ChildProblemUpdateRequest( - Long id, + Long childProblemId, String imageUrl, AnswerType answerType, String answer, diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java index 2496345..56a2d2e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/PresignedUrlResponse.java @@ -1,6 +1,9 @@ package com.moplus.moplus_server.domain.problem.dto.response; +import jakarta.validation.constraints.NotNull; + public record PresignedUrlResponse( + @NotNull(message = "사전 서명된 URL은 필수입니다") String presignedUrl ) { public static PresignedUrlResponse of(String presignedUrl) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java index 246bb54..12c0f23 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java @@ -8,8 +8,9 @@ @Getter @NoArgsConstructor public class ProblemSearchGetResponse { - private Long id; @NotNull(message = "문항 ID는 필수입니다") + private Long id; + @NotNull(message = "문항 custom ID는 필수입니다") private String problemCustomId; private String memo; private String mainProblemImageUrl; diff --git a/src/main/java/com/moplus/moplus_server/global/response/IdResponse.java b/src/main/java/com/moplus/moplus_server/global/response/IdResponse.java index 8c44006..8aab8b7 100644 --- a/src/main/java/com/moplus/moplus_server/global/response/IdResponse.java +++ b/src/main/java/com/moplus/moplus_server/global/response/IdResponse.java @@ -1,6 +1,9 @@ package com.moplus.moplus_server.global.response; +import jakarta.validation.constraints.NotNull; + public record IdResponse( + @NotNull(message = "ID는 필수입니다") Long id ) { } From 30d0a6da74b987d2cb539ca197c0f531a0615bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 20 Feb 2025 20:17:32 +0900 Subject: [PATCH 097/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=ED=95=84=EC=88=98=EA=B0=92=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/mapper/ChildProblemMapperImpl.java | 2 +- .../domain/problem/domain/Answer.java | 10 ---------- .../service/mapper/ChildProblemMapper.java | 2 +- .../auth/controller/AuthControllerTest.java | 2 +- .../service/ProblemUpdateServiceTest.java | 10 +++++----- src/test/resources/insert-problem.sql | 2 +- src/test/resources/insert-problem2.sql | 18 +++++++++++------- 7 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java index e5b5da2..c25760e 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java @@ -10,7 +10,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-20T02:36:18+0900", + date = "2025-02-20T20:15:43+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java index ae8764c..8f2d5aa 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java @@ -31,15 +31,5 @@ private void validateByType(String answer, AnswerType answerType) { throw new InvalidValueException(ErrorCode.INVALID_MULTIPLE_CHOICE_ANSWER); } } - if (answerType == AnswerType.SHORT_ANSWER) { - try { - int numericAnswer = Integer.parseInt(answer); - if (numericAnswer < 0 || numericAnswer > 999) { - throw new InvalidValueException(ErrorCode.INVALID_SHORT_NUMBER_ANSWER); - } - } catch (NumberFormatException e) { - throw new InvalidValueException(ErrorCode.INVALID_SHORT_NUMBER_ANSWER); - } - } } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java index e2deb5a..b48abe5 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapper.java @@ -11,6 +11,6 @@ public interface ChildProblemMapper { ChildProblem from(ChildProblemPostRequest request); - @Mapping(target = "id", source = "id") + @Mapping(target = "id", source = "childProblemId") ChildProblem from(ChildProblemUpdateRequest request); } diff --git a/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java b/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java index d8f3c5e..119749a 100644 --- a/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/auth/controller/AuthControllerTest.java @@ -58,7 +58,7 @@ class 어드민_로그인 { .contentType("application/json") .content(requestBody)) .andExpect(status().isOk()) // HTTP 200 응답 확인 - .andExpect(jsonPath("$.accessToken").isNotEmpty()) // accessToken 필드 존재 여부 확인 + .andExpect(jsonPath("$.data.accessToken").isNotEmpty()) // accessToken 필드 존재 여부 확인 .andExpect(cookie().exists("refreshToken")) // 리프레시 토큰 쿠키 존재 확인 .andExpect(cookie().httpOnly("refreshToken", true)) // HTTP Only 설정 확인 .andExpect(cookie().secure("refreshToken", true)) // Secure 설정 확인 diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java index a9d2679..6a2fa18 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java @@ -55,7 +55,7 @@ void setUp() { ChildProblemUpdateRequest updateChildProblem2 = new ChildProblemUpdateRequest( 2L, "updatedChild2.png", - AnswerType.SHORT_STRING_ANSWER, + AnswerType.SHORT_ANSWER, "23", Set.of(3L, 4L) ); @@ -75,7 +75,7 @@ void setUp() { "updatedReadingTip.png", "updatedSeniorTip.png", List.of("prescription1.png", "prescription2.png"), - AnswerType.SHORT_STRING_ANSWER, + AnswerType.SHORT_ANSWER, List.of(updateChildProblem1, updateChildProblem2), 30, 45 @@ -114,7 +114,7 @@ class 문제_업데이트_정상_동작 { .containsExactly("prescription1.png", "prescription2.png"); // 답안 유형 검증 - assertThat(response.answerType()).isEqualTo(AnswerType.SHORT_STRING_ANSWER); + assertThat(response.answerType()).isEqualTo(AnswerType.SHORT_ANSWER); Problem updatedProblem = problemRepository.findByIdElseThrow(1L); @@ -132,7 +132,7 @@ class 문제_업데이트_정상_동작 { // 두 번째 자식 문제 검증 (새로 추가된 문제) ChildProblem newChild = childProblems.get(1); assertThat(newChild.getImageUrl()).isEqualTo("updatedChild2.png"); - assertThat(newChild.getAnswerType()).isEqualTo(AnswerType.SHORT_STRING_ANSWER); + assertThat(newChild.getAnswerType()).isEqualTo(AnswerType.SHORT_ANSWER); assertThat(newChild.getAnswer()).isEqualTo("23"); assertThat(newChild.getConceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(3L, 4L)); @@ -173,7 +173,7 @@ class 문제_업데이트_예외_처리 { "updatedReadingTip.png", "updatedSeniorTip.png", List.of("prescription1.png"), - AnswerType.SHORT_STRING_ANSWER, + AnswerType.SHORT_ANSWER, List.of(), 30, 45 diff --git a/src/test/resources/insert-problem.sql b/src/test/resources/insert-problem.sql index 0e6a2d4..1e8006c 100644 --- a/src/test/resources/insert-problem.sql +++ b/src/test/resources/insert-problem.sql @@ -47,7 +47,7 @@ INSERT INTO child_problem (child_problem_id, answer, sequence) VALUES (1, 1, 'child1.png', 'MULTIPLE_CHOICE', '1', 0), - (2, 1, 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 1); + (2, 1, 'child2.png', 'SHORT_ANSWER', '정답2', 1); -- 문제-컨셉 태그 연결 테이블 생성 CREATE TABLE IF NOT EXISTS problem_concept ( diff --git a/src/test/resources/insert-problem2.sql b/src/test/resources/insert-problem2.sql index ee909a8..28340d6 100644 --- a/src/test/resources/insert-problem2.sql +++ b/src/test/resources/insert-problem2.sql @@ -1,7 +1,11 @@ -DELETE FROM child_problem_concept; -DELETE FROM problem_concept; -DELETE FROM child_problem; -DELETE FROM problem; +DELETE +FROM child_problem_concept; +DELETE +FROM problem_concept; +DELETE +FROM child_problem; +DELETE +FROM problem; -- problem 데이터 삽입 INSERT INTO problem (problem_id, @@ -35,7 +39,7 @@ VALUES (1, '24052001001', 1, 1, 'GICHUL_PROBLEM', '제목1', '1', 5, '기존 문 (3, '24052001003', 1, 3, 'GICHUL_PROBLEM', '제목3', '3', 3, '문제 3 설명', 'mainProblem3.png', 'mainAnalysis3.png', 'mainHandwriting3.png', 'readingTip3.png', 'seniorTip3.png', - 'prescription5.png, prescription6.png', 'SHORT_STRING_ANSWER', true, + 'prescription5.png, prescription6.png', 'SHORT_ANSWER', true, 15, 45); -- 자식 문제 데이터 삽입 @@ -46,9 +50,9 @@ INSERT INTO child_problem (child_problem_id, answer, sequence) VALUES (1, 1, 'child1.png', 'MULTIPLE_CHOICE', '1', 0), - (2, 1, 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 1), + (2, 1, 'child2.png', 'SHORT_ANSWER', '정답2', 1), (3, 2, 'child3.png', 'MULTIPLE_CHOICE', '2', 0), - (4, 3, 'child4.png', 'SHORT_STRING_ANSWER', '3', 0); + (4, 3, 'child4.png', 'SHORT_ANSWER', '3', 0); -- 문제-컨셉 태그 연결 INSERT INTO problem_concept (problem_id, concept_tag_id) From 0d277026948f78d682580cf2aac835abe20430dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 20 Feb 2025 21:33:37 +0900 Subject: [PATCH 098/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=EC=8B=9C=EA=B0=84=200=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problem/domain/childProblem/ChildProblem.java | 2 +- .../domain/problem/domain/problem/Problem.java | 14 +++++++------- .../problem/domain/problem/RecommendedTime.java | 4 ++-- .../repository/converter/StringListConverter.java | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java index aff64c1..9fd9f47 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java @@ -61,7 +61,7 @@ public static ChildProblem createEmptyChildProblem() { } public void validateAnswerByType(String answer, AnswerType answerType) { - if (this.answerType == AnswerType.MULTIPLE_CHOICE) { + if (answerType == AnswerType.MULTIPLE_CHOICE) { if (!answer.matches("^[1-5]*$")) { throw new InvalidValueException(ErrorCode.INVALID_MULTIPLE_CHOICE_ANSWER); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index f4257c2..64e9d8e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -89,10 +89,10 @@ public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType List<String> prescriptionImageUrls, String seniorTipImageUrl, String readingTipImageUrl, String mainAnalysisImageUrl, String mainProblemImageUrl, String memo, String answer, String title, ProblemType problemType, int number, PracticeTestTag practiceTestTag, - ProblemCustomId problemCustomId, Integer recommendedMinute, Integer recommendedSecond) { + ProblemCustomId problemCustomId) { this.childProblems = childProblems; this.isConfirmed = isConfirmed; - this.answerType = answerType; + this.answerType = AnswerType.SHORT_ANSWER; this.conceptTagIds = conceptTagIds; this.mainHandwritingExplanationImageUrl = mainHandwritingExplanationImageUrl; this.prescriptionImageUrls = prescriptionImageUrls; @@ -102,17 +102,17 @@ public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType this.mainProblemImageUrl = mainProblemImageUrl; this.difficulty = new Difficulty(difficulty); this.memo = memo; - this.answer = new Answer(answer, this.answerType); + this.answer = new Answer("0", this.answerType); this.title = new Title(title); this.problemType = problemType; this.number = number; this.practiceTestId = practiceTestTag != null ? practiceTestTag.getId() : null; this.problemCustomId = problemCustomId; - this.recommendedTime = new RecommendedTime(recommendedMinute, recommendedSecond); + this.recommendedTime = new RecommendedTime(0, 0); } public String getAnswer() { - return answer.getValue(); + return this.answer != null ? answer.getValue() : null; } public void update(Problem inputProblem) { @@ -133,8 +133,8 @@ public void update(Problem inputProblem) { this.prescriptionImageUrls = inputProblem.getPrescriptionImageUrls(); this.answerType = inputProblem.getAnswerType(); this.recommendedTime = new RecommendedTime( - inputProblem.getRecommendedTime() != null ? inputProblem.getRecommendedTime().getMinute() : null, - inputProblem.getRecommendedTime() != null ? inputProblem.getRecommendedTime().getSecond() : null + inputProblem.getRecommendedTime().getMinute(), + inputProblem.getRecommendedTime().getSecond() ); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/RecommendedTime.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/RecommendedTime.java index 8c0b8f7..abe8ab8 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/RecommendedTime.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/RecommendedTime.java @@ -19,8 +19,8 @@ public class RecommendedTime { public RecommendedTime(Integer minute, Integer second) { validateTime(minute, second); - this.minute = minute; - this.second = second; + this.minute = minute != null ? minute : 0; + this.second = second != null ? second : 0; } private void validateTime(Integer minute, Integer second) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java index 9ca476b..bcb08fa 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java @@ -19,7 +19,7 @@ public class StringListConverter implements AttributeConverter<List<String>, Str public String convertToDatabaseColumn(List<String> attribute) { try { if (attribute == null || attribute.isEmpty()) { - return ""; + return "[]"; } return attribute.stream() .filter(str -> str != null && !str.isEmpty()) From d482b2f6e89e60229eb65a3b28e7cb8232b73f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 20 Feb 2025 21:38:38 +0900 Subject: [PATCH 099/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=EC=8B=9C=EA=B0=84=200=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problem/domain/problem/Problem.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index 64e9d8e..568ba30 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -89,10 +89,10 @@ public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType List<String> prescriptionImageUrls, String seniorTipImageUrl, String readingTipImageUrl, String mainAnalysisImageUrl, String mainProblemImageUrl, String memo, String answer, String title, ProblemType problemType, int number, PracticeTestTag practiceTestTag, - ProblemCustomId problemCustomId) { + ProblemCustomId problemCustomId, Integer recommendedMinute, Integer recommendedSecond) { this.childProblems = childProblems; this.isConfirmed = isConfirmed; - this.answerType = AnswerType.SHORT_ANSWER; + this.answerType = answerType; this.conceptTagIds = conceptTagIds; this.mainHandwritingExplanationImageUrl = mainHandwritingExplanationImageUrl; this.prescriptionImageUrls = prescriptionImageUrls; @@ -102,13 +102,13 @@ public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType this.mainProblemImageUrl = mainProblemImageUrl; this.difficulty = new Difficulty(difficulty); this.memo = memo; - this.answer = new Answer("0", this.answerType); + this.answer = new Answer(answer, this.answerType); this.title = new Title(title); this.problemType = problemType; this.number = number; this.practiceTestId = practiceTestTag != null ? practiceTestTag.getId() : null; this.problemCustomId = problemCustomId; - this.recommendedTime = new RecommendedTime(0, 0); + this.recommendedTime = new RecommendedTime(recommendedMinute, recommendedSecond); } public String getAnswer() { From 8e30bbda891bb03e7a9580c8c924b70dc158a7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 01:43:36 +0900 Subject: [PATCH 100/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/childProblem/ChildProblem.java | 2 +- .../domain/problem/domain/problem/Problem.java | 16 +++++++++++++++- .../problem/dto/response/ProblemGetResponse.java | 2 ++ .../converter/StringListConverter.java | 2 +- .../problem/service/ChildProblemService.java | 7 +++++-- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java index 9fd9f47..08c83c5 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java @@ -69,7 +69,7 @@ public void validateAnswerByType(String answer, AnswerType answerType) { } public void update(ChildProblem input) { - if (this.id != input.id) { + if (!this.id.equals(input.id)) { throw new InvalidValueException(ErrorCode.INVALID_CHILD_PROBLEM_SEQUENCE); } this.imageUrl = input.imageUrl; diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index 568ba30..ee6a5bc 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -139,7 +139,7 @@ public void update(Problem inputProblem) { } public void updateChildProblem(List<ChildProblem> inputChildProblems) { - if (isConfirmed && this.childProblems.size() != inputChildProblems.size()) { + if (this.childProblems.size() != inputChildProblems.size()) { throw new InvalidValueException(ErrorCode.INVALID_CHILD_PROBLEM_SIZE); } @@ -148,6 +148,20 @@ public void updateChildProblem(List<ChildProblem> inputChildProblems) { } } + public void addChildProblem(ChildProblem childProblem) { + if (this.isConfirmed) { + throw new InvalidValueException(ErrorCode.INVALID_CHILD_PROBLEM_SIZE); + } + this.childProblems.add(childProblem); + } + + public void deleteChildProblem(Long childProblemId) { + if (this.isConfirmed) { + throw new InvalidValueException(ErrorCode.INVALID_CHILD_PROBLEM_SIZE); + } + this.childProblems.removeIf(childProblem -> childProblem.getId().equals(childProblemId)); + } + public boolean isValid() { return problemCustomId != null && practiceTestId != null diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java index 426f097..06bb301 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java @@ -16,6 +16,7 @@ public record ProblemGetResponse( String problemCustomId, @NotNull(message = "컬렉션 값은 필수입니다.") Set<Long> conceptTagIds, + boolean isConfirmed, Long practiceTestId, int number, Integer difficulty, @@ -43,6 +44,7 @@ public static ProblemGetResponse of(Problem problem) { .id(problem.getId()) .problemCustomId(problem.getProblemCustomId()) .conceptTagIds(problem.getConceptTagIds()) + .isConfirmed(problem.isConfirmed()) .practiceTestId(problem.getPracticeTestId()) .number(problem.getNumber()) .answer(problem.getAnswer()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java index bcb08fa..9ca476b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java @@ -19,7 +19,7 @@ public class StringListConverter implements AttributeConverter<List<String>, Str public String convertToDatabaseColumn(List<String> attribute) { try { if (attribute == null || attribute.isEmpty()) { - return "[]"; + return ""; } return attribute.stream() .filter(str -> str != null && !str.isEmpty()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ChildProblemService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ChildProblemService.java index d4e7a43..bdc1f6a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ChildProblemService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ChildProblemService.java @@ -24,7 +24,9 @@ public Long createChildProblem(Long problemId) { throw new InvalidValueException(ErrorCode.CHILD_PROBLEM_UPDATE_AFTER_CONFIRMED); } - return childProblemRepository.save(ChildProblem.createEmptyChildProblem()).getId(); + problem.addChildProblem(ChildProblem.createEmptyChildProblem()); + + return problemRepository.save(problem).getChildProblems().get(problem.getChildProblems().size() - 1).getId(); } @Transactional @@ -33,6 +35,7 @@ public void deleteChildProblem(Long problemId, Long childProblemId) { if (problem.isConfirmed()) { throw new InvalidValueException(ErrorCode.CHILD_PROBLEM_UPDATE_AFTER_CONFIRMED); } - childProblemRepository.deleteById(childProblemId); + problem.deleteChildProblem(childProblemId); + problemRepository.save(problem); } } From b096a857fdd19148d278289e9274dd4464203fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 03:49:17 +0900 Subject: [PATCH 101/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20memo=20->=20title?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/mapper/ChildProblemMapperImpl.java | 2 +- .../problem/service/mapper/ProblemMapperImpl.java | 2 +- .../controller/ProblemSearchController.java | 4 ++-- .../dto/response/ProblemSearchGetResponse.java | 6 +++--- .../repository/ProblemSearchRepositoryCustom.java | 14 +++++++------- .../dto/response/ProblemSetSearchGetResponse.java | 4 +++- .../ProblemSetSearchRepositoryCustom.java | 2 ++ .../ProblemSearchRepositoryCustomTest.java | 4 ++-- 8 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java index c25760e..e682008 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java @@ -10,7 +10,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-20T20:15:43+0900", + date = "2025-02-21T03:13:14+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index 428b7de..4007f06 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -15,7 +15,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-20T02:36:19+0900", + date = "2025-02-21T03:13:14+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java index 7deed3b..1ddf542 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemSearchController.java @@ -27,10 +27,10 @@ public class ProblemSearchController { ) public ResponseEntity<List<ProblemSearchGetResponse>> search( @RequestParam(value = "problemCustomId", required = false) String problemCustomId, - @RequestParam(value = "memo", required = false) String memo, + @RequestParam(value = "title", required = false) String title, @RequestParam(value = "conceptTagIds", required = false) List<Long> conceptTagIds ) { - List<ProblemSearchGetResponse> problems = problemSearchRepository.search(problemCustomId, memo, + List<ProblemSearchGetResponse> problems = problemSearchRepository.search(problemCustomId, title, conceptTagIds); return ResponseEntity.ok(problems); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java index 12c0f23..a951906 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java @@ -12,16 +12,16 @@ public class ProblemSearchGetResponse { private Long id; @NotNull(message = "문항 custom ID는 필수입니다") private String problemCustomId; - private String memo; + private String title; private String mainProblemImageUrl; @NotNull(message = "개념 태그리스트는 필수입니다") private Set<ConceptTagSearchResponse> conceptTagResponses; - public ProblemSearchGetResponse(Long id, String problemCustomId, String memo, String mainProblemImageUrl, + public ProblemSearchGetResponse(Long id, String problemCustomId, String title, String mainProblemImageUrl, Set<ConceptTagSearchResponse> conceptTagResponses) { this.id = id; this.problemCustomId = problemCustomId; - this.memo = memo; + this.title = title; this.mainProblemImageUrl = mainProblemImageUrl; this.conceptTagResponses = conceptTagResponses; } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java index 3457bce..f4c44d2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java @@ -21,16 +21,16 @@ public class ProblemSearchRepositoryCustom { private final JPAQueryFactory queryFactory; - public List<ProblemSearchGetResponse> search(String problemId, String memo, List<Long> conceptTagIds) { + public List<ProblemSearchGetResponse> search(String problemId, String title, List<Long> conceptTagIds) { return queryFactory - .select(problem.problemCustomId.id, problem.memo, problem.mainProblemImageUrl) + .select(problem.problemCustomId.id, problem.title, problem.mainProblemImageUrl) .from(problem) .leftJoin(childProblem).on(childProblem.in(problem.childProblems)) .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds) .or(conceptTag.id.in(childProblem.conceptTagIds))) .where( containsProblemId(problemId), - containsName(memo), + containsName(title), hasConceptTags(conceptTagIds) ) .distinct() @@ -38,7 +38,7 @@ public List<ProblemSearchGetResponse> search(String problemId, String memo, List Projections.constructor(ProblemSearchGetResponse.class, problem.id, problem.problemCustomId.id, - problem.memo, + problem.title.title, problem.mainProblemImageUrl, GroupBy.set( Projections.constructor(ConceptTagSearchResponse.class, @@ -81,11 +81,11 @@ private BooleanExpression containsProblemId(String problemId) { } //name 조건 (포함 검색) - private BooleanExpression containsName(String comment) { - if (comment == null || comment.trim().isEmpty()) { + private BooleanExpression containsName(String title) { + if (title == null || title.trim().isEmpty()) { return null; } - return problem.memo.containsIgnoreCase(comment.trim()); + return problem.title.title.containsIgnoreCase(title.trim()); } //conceptTagIds 조건 (하나라도 포함되면 조회) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java index 5803ad1..49f0e6b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java @@ -10,6 +10,7 @@ @Getter @NoArgsConstructor public class ProblemSetSearchGetResponse { + private Long id; private String problemSetTitle; private ProblemSetConfirmStatus confirmStatus; private LocalDate publishedDate; @@ -17,9 +18,10 @@ public class ProblemSetSearchGetResponse { private List<ProblemThumbnailResponse> problemThumbnailResponses; public ProblemSetSearchGetResponse( - String problemSetTitle, ProblemSetConfirmStatus confirmStatus, LocalDate publishedDate, + Long id, String problemSetTitle, ProblemSetConfirmStatus confirmStatus, LocalDate publishedDate, List<ProblemThumbnailResponse> problemThumbnailResponses ) { + this.id = id; this.problemSetTitle = problemSetTitle; this.confirmStatus = confirmStatus; this.publishedDate = publishedDate; diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index b93d688..c6fcb2c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -35,6 +35,7 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p .distinct() .transform(GroupBy.groupBy(problemSet.id).list( Projections.constructor(ProblemSetSearchGetResponse.class, + problemSet.id, problemSet.title.value, problemSet.confirmStatus, publish.publishedDate, // 발행되지 않은 경우 null 반환 @@ -63,6 +64,7 @@ public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, S .distinct() .transform(GroupBy.groupBy(problemSet.id).list( Projections.constructor(ProblemSetSearchGetResponse.class, + problemSet.id, problemSet.title.value, problemSet.confirmStatus, publish.publishedDate, // 발행되지 않은 경우 null 반환 diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java index d586582..b4857b9 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java @@ -34,7 +34,7 @@ public class ProblemSearchRepositoryCustomTest { @Test void name_포함_검색() { // when - List<ProblemSearchGetResponse> result = problemSearchRepository.search(null, "설명 1 ", null); + List<ProblemSearchGetResponse> result = problemSearchRepository.search(null, "제목1 ", null); // then assertThat(result).hasSize(1); @@ -55,7 +55,7 @@ public class ProblemSearchRepositoryCustomTest { @Test void problemId_이름_conceptTagIds_모두_적용된_검색() { // when - List<ProblemSearchGetResponse> result = problemSearchRepository.search("12240520", "설명 1", List.of(1L)); + List<ProblemSearchGetResponse> result = problemSearchRepository.search("12240520", "제목1", List.of(1L)); // then assertThat(result).hasSize(1); From 1aadf80a1d8d0df84b669251b57bb7bf3cc4d224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 03:55:00 +0900 Subject: [PATCH 102/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=20?= =?UTF-8?q?=EC=84=B8=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/v0/TestResult/entity/QIncorrectProblem.java | 2 +- .../v0/practiceTest/domain/QProblemImageForTest.java | 2 +- .../problemset/dto/response/ProblemSummaryResponse.java | 8 ++++++-- .../problemset/service/ProblemSetGetServiceTest.java | 6 +++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java index b73be8c..9f3b995 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/TestResult/entity/QIncorrectProblem.java @@ -37,7 +37,7 @@ public class QIncorrectProblem extends EntityPathBase<IncorrectProblem> { public final NumberPath<Long> practiceTestId = createNumber("practiceTestId", Long.class); - public final NumberPath<Long> problemId = createNumber("problemId", Long.class); + public final NumberPath<Long> problemId = createNumber("problemCustomId", Long.class); public final StringPath problemNumber = createString("problemNumber"); diff --git a/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java index b7e7668..67b69aa 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java +++ b/src/main/generated/com/moplus/moplus_server/domain/v0/practiceTest/domain/QProblemImageForTest.java @@ -25,7 +25,7 @@ public class QProblemImageForTest extends EntityPathBase<ProblemImageForTest> { public final StringPath imageUrl = createString("imageUrl"); - public final NumberPath<Long> problemId = createNumber("problemId", Long.class); + public final NumberPath<Long> problemId = createNumber("problemCustomId", Long.class); public QProblemImageForTest(String variable) { super(ProblemImageForTest.class, forVariable(variable)); diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java index 4bb2cfe..dc6a197 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -7,7 +7,10 @@ @Builder public record ProblemSummaryResponse( - String problemId, + @NotNull(message = "문항 ID는 필수입니다") + Long problemId, + @NotNull(message = "문항 custom ID는 필수입니다") + String problemCustomId, int number, String practiceTestName, String memo, @@ -18,7 +21,8 @@ public record ProblemSummaryResponse( public static ProblemSummaryResponse of(Problem problem, String practiceTestName, List<String> tagNames) { return ProblemSummaryResponse.builder() - .problemId(problem.getProblemCustomId()) + .problemId(problem.getId()) + .problemCustomId(problem.getProblemCustomId()) .number(problem.getNumber()) .memo(problem.getMemo()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java index 20dc272..e3c9a78 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java @@ -57,7 +57,7 @@ void setUp() { assertThat(response).isNotNull(); assertThat(response.title()).isEqualTo("테스트 문항세트"); assertThat(response.problemSummaries()).hasSize(1); - assertThat(response.problemSummaries().get(0).problemId()).isEqualTo("1224052001"); + assertThat(response.problemSummaries().get(0).problemCustomId()).isEqualTo("1224052001"); assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); } @@ -88,12 +88,12 @@ void setUp() { assertThat(response.problemSummaries()).hasSize(2); // 첫 번째 문제 검증 - assertThat(response.problemSummaries().get(0).problemId()).isEqualTo("1224052001"); + assertThat(response.problemSummaries().get(0).problemCustomId()).isEqualTo("1224052001"); assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); // 두 번째 문제 검증 - assertThat(response.problemSummaries().get(1).problemId()).isEqualTo("1224052002"); + assertThat(response.problemSummaries().get(1).problemCustomId()).isEqualTo("1224052002"); assertThat(response.problemSummaries().get(1).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(1).tagNames()).contains("미분 개념", "삼각함수 개념"); } From 72a9644a08f2ac3257df54a7c82ffd7df1f9a2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 04:09:01 +0900 Subject: [PATCH 103/115] =?UTF-8?q?[fix/#54]=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20id=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problemset/controller/ProblemSetController.java | 4 ++-- .../publish/dto/response/PublishMonthGetResponse.java | 8 +++++--- .../domain/publish/service/PublishGetService.java | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java index 0b8296e..bf3e4f2 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -60,7 +60,7 @@ public ResponseEntity<Void> updateProblemSet( @RequestBody ProblemSetUpdateRequest request ) { problemSetUpdateService.updateProblemSet(problemSetId, request); - return ResponseEntity.noContent().build(); + return ResponseEntity.ok(null); } @DeleteMapping("/{problemSetId}") @@ -69,7 +69,7 @@ public ResponseEntity<Void> deleteProblemSet( @PathVariable Long problemSetId ) { problemSetDeleteService.deleteProblemSet(problemSetId); - return ResponseEntity.noContent().build(); + return ResponseEntity.ok(null); } @PutMapping("/{problemSetId}/confirm") diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java index 8b399a0..8ca3189 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java @@ -1,16 +1,18 @@ package com.moplus.moplus_server.domain.publish.dto.response; +import com.moplus.moplus_server.domain.publish.domain.Publish; import lombok.Builder; @Builder public record PublishMonthGetResponse( + Long publishId, int day, PublishProblemSetResponse problemSetInfo ) { - public static PublishMonthGetResponse of(int day, PublishProblemSetResponse problemSetInfos) { - + public static PublishMonthGetResponse of(Publish publish, PublishProblemSetResponse problemSetInfos) { return PublishMonthGetResponse.builder() - .day(day) + .publishId(publish.getId()) + .day(publish.getPublishedDate().getDayOfMonth()) .problemSetInfo(problemSetInfos) .build(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java index 6cc6c52..bca2511 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java @@ -58,7 +58,7 @@ private PublishMonthGetResponse convertToResponse(Publish publish, Map<Long, Pro throw new InvalidValueException(ErrorCode.PROBLEM_SET_NOT_FOUND); } return PublishMonthGetResponse.of( - publish.getPublishedDate().getDayOfMonth(), + publish, PublishProblemSetResponse.of(problemSet) ); } From a9ba4be354340bf5845009cb1b917fe9ebb4c548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 04:13:18 +0900 Subject: [PATCH 104/115] =?UTF-8?q?[fix/#54]=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=9D=91=EB=8B=B5=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/publish/controller/PublishController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java index ddd8728..7a53f8d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java @@ -53,6 +53,6 @@ public ResponseEntity<Void> deleteProblemSet( @PathVariable Long publishId ) { publishDeleteService.deletePublish(publishId); - return ResponseEntity.noContent().build(); + return ResponseEntity.ok(null); } } From 0243ef6c7c122b72820ae2c3c861d6f98fd33a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 04:20:18 +0900 Subject: [PATCH 105/115] =?UTF-8?q?[fix/#54]=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?=EC=A7=80=EB=82=9C=20=EB=B0=9C=ED=96=89=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B6=88=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moplus/moplus_server/domain/publish/domain/Publish.java | 6 ++++++ .../domain/publish/service/PublishDeleteService.java | 2 ++ .../moplus_server/global/error/exception/ErrorCode.java | 1 + 3 files changed, 9 insertions(+) diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java b/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java index 52aca88..3ea566c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java @@ -42,4 +42,10 @@ public void validatePublishedDate() { throw new InvalidValueException(ErrorCode.INVALID_DATE_ERROR); } } + + public void validateDeletable() { + if (this.publishedDate.isBefore(LocalDate.now())) { + throw new InvalidValueException(ErrorCode.CANNOT_DELETE_PAST_PUBLISH); + } + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java index 75d9e9c..039ac00 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java @@ -15,6 +15,8 @@ public class PublishDeleteService { @Transactional public void deletePublish(Long publishId) { Publish publish = publishRepository.findByIdElseThrow(publishId); + publish.validateDeletable(); + publishRepository.delete(publish); } } diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index f3c83b2..bd57267 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -67,6 +67,7 @@ public enum ErrorCode { INVALID_MONTH_ERROR(HttpStatus.BAD_REQUEST, "유효하지 않은 월입니다."), INVALID_DATE_ERROR(HttpStatus.BAD_REQUEST, "오늘 이후 날짜에만 발행이 가능합니다."), PUBLISH_NOT_FOUND(HttpStatus.NOT_FOUND, "발행 정보를 찾을 수 없습니다"), + CANNOT_DELETE_PAST_PUBLISH(HttpStatus.BAD_REQUEST, "이미 지난 발행건은 삭제할 수 없습니다."), ; From 1632b5537b507a21f5cd15aaf807b097ae488edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 04:27:50 +0900 Subject: [PATCH 106/115] =?UTF-8?q?[fix/#54]=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=8B=9C=20=EC=BB=A8=ED=8E=8C=20=ED=95=B4=EC=A0=9C=20=EB=B6=88?= =?UTF-8?q?=EA=B0=80,=20=EC=84=B8=ED=8A=B8=20=EB=AC=B8=ED=95=AD=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B0=9C=ED=96=89=20=EB=82=A0=EC=A7=9C=20=EC=97=AC?= =?UTF-8?q?=EB=9F=AC=EA=B0=9C=20=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problemset/dto/response/ProblemSetGetResponse.java | 6 +++--- .../domain/problemset/service/ProblemSetGetService.java | 6 +++--- .../problemset/service/ProblemSetUpdateService.java | 8 ++++++++ .../domain/publish/repository/PublishRepository.java | 3 +-- .../moplus_server/global/error/exception/ErrorCode.java | 1 + 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java index ab71eae..e69f3bf 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java @@ -12,18 +12,18 @@ public record ProblemSetGetResponse( Long id, String title, ProblemSetConfirmStatus confirmStatus, - LocalDate publishedDate, + List<LocalDate> publishedDates, @NotNull(message = "컬렉션 값은 필수입니다.") List<ProblemSummaryResponse> problemSummaries ) { - public static ProblemSetGetResponse of(ProblemSet problemSet, LocalDate publishedDate, + public static ProblemSetGetResponse of(ProblemSet problemSet, List<LocalDate> publishedDates, List<ProblemSummaryResponse> problemSummaries) { return ProblemSetGetResponse.builder() .id(problemSet.getId()) .title(problemSet.getTitle().getValue()) .confirmStatus(problemSet.getConfirmStatus()) - .publishedDate(publishedDate) + .publishedDates(publishedDates) .problemSummaries(problemSummaries) .build(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java index 16e1d2d..2574719 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java @@ -33,9 +33,9 @@ public class ProblemSetGetService { public ProblemSetGetResponse getProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); - LocalDate publishedDate = publishRepository.findByProblemSetId(problemSetId) + List<LocalDate> publishedDates = publishRepository.findByProblemSetId(problemSetId).stream() .map(Publish::getPublishedDate) - .orElse(null); + .toList(); List<ProblemSummaryResponse> problemSummaries = new ArrayList<>(); for (Long problemId : problemSet.getProblemIds()) { @@ -47,6 +47,6 @@ public ProblemSetGetResponse getProblemSet(Long problemSetId) { .toList(); problemSummaries.add(ProblemSummaryResponse.of(problem, practiceTestTag.getName(), tagNames)); } - return ProblemSetGetResponse.of(problemSet, publishedDate, problemSummaries); + return ProblemSetGetResponse.of(problemSet, publishedDates, problemSummaries); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java index e588c82..5e01a9c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java @@ -7,6 +7,8 @@ import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.util.ArrayList; @@ -21,6 +23,7 @@ public class ProblemSetUpdateService { private final ProblemSetRepository problemSetRepository; private final ProblemRepository problemRepository; + private final PublishRepository publishRepository; @Transactional public void reorderProblems(Long problemSetId, ProblemReorderRequest request) { @@ -46,6 +49,11 @@ public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) @Transactional public ProblemSetConfirmStatus toggleConfirmProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + List<Publish> publishes = publishRepository.findByProblemSetId(problemSetId); + if (!publishes.isEmpty()) { + throw new InvalidValueException(ErrorCode.ALREADY_PUBLISHED_ERROR); + } + List<Problem> problems = new ArrayList<>(); for (Long problemId : problemSet.getProblemIds()) { Problem problem = problemRepository.findByIdElseThrow(problemId); diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java b/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java index 0fb157d..9168947 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java @@ -5,7 +5,6 @@ import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.time.LocalDate; import java.util.List; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface PublishRepository extends JpaRepository<Publish, Long> { @@ -15,5 +14,5 @@ default Publish findByIdElseThrow(Long publishId) { return findById(publishId).orElseThrow(() -> new NotFoundException(ErrorCode.PUBLISH_NOT_FOUND)); } - Optional<Publish> findByProblemSetId(Long problemSetId); + List<Publish> findByProblemSetId(Long problemSetId); } diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index bd57267..91e42ac 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -68,6 +68,7 @@ public enum ErrorCode { INVALID_DATE_ERROR(HttpStatus.BAD_REQUEST, "오늘 이후 날짜에만 발행이 가능합니다."), PUBLISH_NOT_FOUND(HttpStatus.NOT_FOUND, "발행 정보를 찾을 수 없습니다"), CANNOT_DELETE_PAST_PUBLISH(HttpStatus.BAD_REQUEST, "이미 지난 발행건은 삭제할 수 없습니다."), + ALREADY_PUBLISHED_ERROR(HttpStatus.BAD_REQUEST, "이미 발행된 문항세트는 컨펌해제할 수 없습니다."), ; From 56e98d64e352fcbc3f5c4c087a8893eeebb0fc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 04:31:43 +0900 Subject: [PATCH 107/115] =?UTF-8?q?[fix/#54]=20=EC=BB=AC=EB=A0=89=EC=85=98?= =?UTF-8?q?=20not=20null=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problemset/dto/response/ProblemSetGetResponse.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java index e69f3bf..c4c47fa 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java @@ -12,6 +12,7 @@ public record ProblemSetGetResponse( Long id, String title, ProblemSetConfirmStatus confirmStatus, + @NotNull(message = "컬렉션 값은 필수입니다.") List<LocalDate> publishedDates, @NotNull(message = "컬렉션 값은 필수입니다.") List<ProblemSummaryResponse> problemSummaries From e3f90dca122797a701782f57e82a2915befb4b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 15:11:28 +0900 Subject: [PATCH 108/115] =?UTF-8?q?[fix/#54]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/ProblemSearchGetResponse.java | 5 ++- .../ProblemSearchRepositoryCustom.java | 1 + .../controller/ProblemSetController.java | 4 +- .../domain/problemset/domain/ProblemSet.java | 7 +++ .../response/ProblemSetSearchGetResponse.java | 1 + .../service/ProblemSetSaveService.java | 14 +----- .../problemset/ProblemSetServiceTest.java | 45 ++++++------------- 7 files changed, 30 insertions(+), 47 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java index a951906..80a1ecb 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java @@ -13,15 +13,18 @@ public class ProblemSearchGetResponse { @NotNull(message = "문항 custom ID는 필수입니다") private String problemCustomId; private String title; + private String memo; private String mainProblemImageUrl; @NotNull(message = "개념 태그리스트는 필수입니다") private Set<ConceptTagSearchResponse> conceptTagResponses; - public ProblemSearchGetResponse(Long id, String problemCustomId, String title, String mainProblemImageUrl, + public ProblemSearchGetResponse(Long id, String problemCustomId, String title, String memo, + String mainProblemImageUrl, Set<ConceptTagSearchResponse> conceptTagResponses) { this.id = id; this.problemCustomId = problemCustomId; this.title = title; + this.memo = memo; this.mainProblemImageUrl = mainProblemImageUrl; this.conceptTagResponses = conceptTagResponses; } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java index f4c44d2..803ef2e 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java @@ -39,6 +39,7 @@ public List<ProblemSearchGetResponse> search(String problemId, String title, Lis problem.id, problem.problemCustomId.id, problem.title.title, + problem.memo, problem.mainProblemImageUrl, GroupBy.set( Projections.constructor(ConceptTagSearchResponse.class, diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java index bf3e4f2..c78c9f3 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetController.java @@ -2,7 +2,6 @@ import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; -import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetGetResponse; import com.moplus.moplus_server.domain.problemset.service.ProblemSetDeleteService; @@ -38,9 +37,8 @@ public class ProblemSetController { @PostMapping("") @Operation(summary = "문항세트 생성", description = "문항세트를 생성합니다. 문항은 요청 순서대로 저장합니다.") public ResponseEntity<IdResponse> createProblemSet( - @RequestBody ProblemSetPostRequest request ) { - return ResponseEntity.ok(new IdResponse(problemSetSaveService.createProblemSet(request))); + return ResponseEntity.ok(new IdResponse(problemSetSaveService.createProblemSet())); } @Hidden diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index 8d339ad..077be3f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -57,6 +57,13 @@ public ProblemSet(String title, List<Long> problemIds) { this.problemIds = problemIds; } + public static ProblemSet ofEmptyProblemSet() { + return ProblemSet.builder() + .title("") + .problemIds(new ArrayList<>()) + .build(); + } + public void updateProblemOrder(List<Long> newProblems) { this.problemIds = new ArrayList<>(newProblems); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java index 49f0e6b..90eda47 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java @@ -10,6 +10,7 @@ @Getter @NoArgsConstructor public class ProblemSetSearchGetResponse { + @NotNull(message = "ID 값은 필수입니다.") private Long id; private String problemSetTitle; private ProblemSetConfirmStatus confirmStatus; diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java index 6df9a8f..2281c09 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -2,10 +2,7 @@ import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; -import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; -import com.moplus.moplus_server.global.error.exception.ErrorCode; -import com.moplus.moplus_server.global.error.exception.InvalidValueException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,17 +15,10 @@ public class ProblemSetSaveService { private final ProblemRepository problemRepository; @Transactional - public Long createProblemSet(ProblemSetPostRequest request) { - // 빈 문항 유효성 검증 - if (request.problems().isEmpty()) { - throw new InvalidValueException(ErrorCode.EMPTY_PROBLEMS_ERROR); - } - - // 모든 문항이 DB에 존재하는지 검증 - request.problems().forEach(problemRepository::findByIdElseThrow); + public Long createProblemSet() { // ProblemSet 생성 - ProblemSet problemSet = request.toEntity(request.problems()); + ProblemSet problemSet = ProblemSet.ofEmptyProblemSet(); return problemSetRepository.save(problemSet).getId(); } diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index 539d0a0..77fc3ab 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -6,7 +6,6 @@ import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest; -import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService; @@ -37,21 +36,24 @@ public class ProblemSetServiceTest { @Autowired private ProblemSetRepository problemSetRepository; - private ProblemSetPostRequest problemSetPostRequest; + private ProblemSetUpdateRequest problemSetUpdateRequest; + private Long problemSetId; @BeforeEach void setUp() { + problemSetId = problemSetSaveService.createProblemSet(); // 초기 문항 세트 생성 요청 데이터 준비 - problemSetPostRequest = new ProblemSetPostRequest( + problemSetUpdateRequest = new ProblemSetUpdateRequest( "초기 문항세트", List.of(1L, 2L, 3L) ); + problemSetUpdateService.updateProblemSet(problemSetId, problemSetUpdateRequest); + + } @Test void 문항세트_생성_테스트() { - // when - Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); // then ProblemSet savedProblemSet = problemSetRepository.findById(problemSetId) @@ -65,7 +67,6 @@ void setUp() { @Test void 문항세트_문항순서_변경_테스트() { // given - Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); // when ProblemReorderRequest reorderRequest = new ProblemReorderRequest( @@ -81,8 +82,6 @@ void setUp() { @Test void 문항세트_업데이트_테스트() { - // given - Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); // when ProblemSetUpdateRequest updateRequest = new ProblemSetUpdateRequest( @@ -101,8 +100,6 @@ void setUp() { @Test void 문항세트_컨펌_토글_테스트() { - // given - Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); // when ProblemSetConfirmStatus firstToggleStatus = problemSetUpdateService.toggleConfirmProblemSet( @@ -117,8 +114,6 @@ void setUp() { @Test void 유효하지_않은_문항이_포함된_문항세트_컨펌_실패_테스트() { - // given - Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); // 유효하지 않은 문항을 포함하도록 설정 (문항 ID가 존재하지 않거나 필수 필드가 누락된 경우) ProblemSetUpdateRequest invalidUpdateRequest = new ProblemSetUpdateRequest( @@ -134,36 +129,24 @@ void setUp() { .hasMessageContaining(ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage()); } - @Test - void 빈_문항리스트_문항세트_생성_실패_테스트() { - // given - ProblemSetPostRequest emptyProblemSetRequest = new ProblemSetPostRequest( - "빈 문항세트", - List.of() // 빈 리스트 - ); - - // when & then - assertThatThrownBy(() -> problemSetSaveService.createProblemSet(emptyProblemSetRequest)) - .isInstanceOf(InvalidValueException.class) - .hasMessageContaining(ErrorCode.EMPTY_PROBLEMS_ERROR.getMessage()); - } - @Test void 빈_제목_문항세트_생성_테스트() { // given - ProblemSetPostRequest emptyTitleRequest = new ProblemSetPostRequest( + ProblemSetUpdateRequest emptyTitleRequest = new ProblemSetUpdateRequest( "", // 빈 문자열 제목 List.of(1L, 2L, 3L) ); - ProblemSetPostRequest nullTitleRequest = new ProblemSetPostRequest( + ProblemSetUpdateRequest nullTitleRequest = new ProblemSetUpdateRequest( null, // null 제목 List.of(1L, 2L, 3L) ); // when - Long emptyTitleProblemSetId = problemSetSaveService.createProblemSet(emptyTitleRequest); - Long nullTitleProblemSetId = problemSetSaveService.createProblemSet(nullTitleRequest); + Long emptyTitleProblemSetId = problemSetSaveService.createProblemSet(); + Long nullTitleProblemSetId = problemSetSaveService.createProblemSet(); + problemSetUpdateService.updateProblemSet(emptyTitleProblemSetId, emptyTitleRequest); + problemSetUpdateService.updateProblemSet(nullTitleProblemSetId, nullTitleRequest); // then ProblemSet emptyTitleSavedProblemSet = problemSetRepository.findByIdElseThrow(emptyTitleProblemSetId); @@ -177,7 +160,7 @@ void setUp() { @Test void 문항세트_빈_제목_업데이트_테스트() { // given - Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); + Long problemSetId = problemSetSaveService.createProblemSet(); ProblemSetUpdateRequest emptyUpdateRequest = new ProblemSetUpdateRequest( "업데이트된 빈 문항세트", From 4a712bb88afcdb271a01523db8fb6061fcc9c2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 16:54:19 +0900 Subject: [PATCH 109/115] =?UTF-8?q?[feat]=20cors=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6e09489..9ef2087 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -21,4 +21,5 @@ cors-allowed-origins: http://localhost:3000, http://localhost:5173, https://www.mopl.kr, - http://www.mopl.kr \ No newline at end of file + http://www.mopl.kr, + https://mopl-admin.vercel.app \ No newline at end of file From 3315fc31f8146f6e9ec71299bf9955717fe61a8c Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Fri, 21 Feb 2025 17:28:57 +0900 Subject: [PATCH 110/115] =?UTF-8?q?[hotfix]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EB=AC=B8=ED=95=AD=20=EC=A4=91=EB=B3=B5=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/ProblemSetSearchGetResponse.java | 5 +---- .../repository/ProblemSetSearchRepositoryCustom.java | 8 -------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java index 90eda47..dd2eef1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java @@ -2,7 +2,6 @@ import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import jakarta.validation.constraints.NotNull; -import java.time.LocalDate; import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,18 +13,16 @@ public class ProblemSetSearchGetResponse { private Long id; private String problemSetTitle; private ProblemSetConfirmStatus confirmStatus; - private LocalDate publishedDate; @NotNull(message = "컬렉션 값은 필수입니다.") private List<ProblemThumbnailResponse> problemThumbnailResponses; public ProblemSetSearchGetResponse( - Long id, String problemSetTitle, ProblemSetConfirmStatus confirmStatus, LocalDate publishedDate, + Long id, String problemSetTitle, ProblemSetConfirmStatus confirmStatus, List<ProblemThumbnailResponse> problemThumbnailResponses ) { this.id = id; this.problemSetTitle = problemSetTitle; this.confirmStatus = confirmStatus; - this.publishedDate = publishedDate; this.problemThumbnailResponses = problemThumbnailResponses; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index c6fcb2c..8ebb041 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -1,10 +1,8 @@ package com.moplus.moplus_server.domain.problemset.repository; -import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag; import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; import static com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus.CONFIRMED; import static com.moplus.moplus_server.domain.problemset.domain.QProblemSet.problemSet; -import static com.moplus.moplus_server.domain.publish.domain.QPublish.publish; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; @@ -26,8 +24,6 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p return queryFactory .from(problemSet) .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 - .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 - .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( containsProblemSetTitle(problemSetTitle), containsProblemTitle(problemTitle) @@ -38,7 +34,6 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p problemSet.id, problemSet.title.value, problemSet.confirmStatus, - publish.publishedDate, // 발행되지 않은 경우 null 반환 GroupBy.list( Projections.constructor(ProblemThumbnailResponse.class, problem.title.title, @@ -54,8 +49,6 @@ public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, S return queryFactory .from(problemSet) .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 - .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 - .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( problemSet.confirmStatus.eq(CONFIRMED), containsProblemSetTitle(problemSetTitle), @@ -67,7 +60,6 @@ public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, S problemSet.id, problemSet.title.value, problemSet.confirmStatus, - publish.publishedDate, // 발행되지 않은 경우 null 반환 GroupBy.list( Projections.constructor(ProblemThumbnailResponse.class, problem.title.title, From 222292de50ae6c374b9c6764f746811994b13701 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Fri, 21 Feb 2025 17:28:57 +0900 Subject: [PATCH 111/115] =?UTF-8?q?[hotfix]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=9C=ED=96=89=EB=82=A0=EC=A7=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/ProblemSetSearchGetResponse.java | 5 +-- .../ProblemSetSearchRepositoryCustom.java | 8 ----- .../ProblemSetSearchRepositoryCustomTest.java | 35 ------------------- 3 files changed, 1 insertion(+), 47 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java index 90eda47..dd2eef1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java @@ -2,7 +2,6 @@ import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import jakarta.validation.constraints.NotNull; -import java.time.LocalDate; import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,18 +13,16 @@ public class ProblemSetSearchGetResponse { private Long id; private String problemSetTitle; private ProblemSetConfirmStatus confirmStatus; - private LocalDate publishedDate; @NotNull(message = "컬렉션 값은 필수입니다.") private List<ProblemThumbnailResponse> problemThumbnailResponses; public ProblemSetSearchGetResponse( - Long id, String problemSetTitle, ProblemSetConfirmStatus confirmStatus, LocalDate publishedDate, + Long id, String problemSetTitle, ProblemSetConfirmStatus confirmStatus, List<ProblemThumbnailResponse> problemThumbnailResponses ) { this.id = id; this.problemSetTitle = problemSetTitle; this.confirmStatus = confirmStatus; - this.publishedDate = publishedDate; this.problemThumbnailResponses = problemThumbnailResponses; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index c6fcb2c..8ebb041 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -1,10 +1,8 @@ package com.moplus.moplus_server.domain.problemset.repository; -import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag; import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; import static com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus.CONFIRMED; import static com.moplus.moplus_server.domain.problemset.domain.QProblemSet.problemSet; -import static com.moplus.moplus_server.domain.publish.domain.QPublish.publish; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; @@ -26,8 +24,6 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p return queryFactory .from(problemSet) .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 - .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 - .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( containsProblemSetTitle(problemSetTitle), containsProblemTitle(problemTitle) @@ -38,7 +34,6 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p problemSet.id, problemSet.title.value, problemSet.confirmStatus, - publish.publishedDate, // 발행되지 않은 경우 null 반환 GroupBy.list( Projections.constructor(ProblemThumbnailResponse.class, problem.title.title, @@ -54,8 +49,6 @@ public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, S return queryFactory .from(problemSet) .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 - .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 - .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( problemSet.confirmStatus.eq(CONFIRMED), containsProblemSetTitle(problemSetTitle), @@ -67,7 +60,6 @@ public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, S problemSet.id, problemSet.title.value, problemSet.confirmStatus, - publish.publishedDate, // 발행되지 않은 경우 null 반환 GroupBy.list( Projections.constructor(ProblemThumbnailResponse.class, problem.title.title, diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index 33bc144..8158ee0 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -107,23 +107,6 @@ public class ProblemSetSearchRepositoryCustomTest { ProblemSetSearchGetResponse response = result.get(0); assertThat(response.getConfirmStatus()).isEqualTo(ProblemSetConfirmStatus.NOT_CONFIRMED); - assertThat(response.getPublishedDate()).isNull(); - } - - @Test - void 발행된_문항세트_발행날짜_테스트() { - // given - LocalDate publishDate = LocalDate.now().plusDays(1); - publishSaveService.createPublish(new PublishPostRequest(publishDate, 2L)); - - // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search("고3 모의고사", null); - - // then - assertThat(result).hasSize(1); - ProblemSetSearchGetResponse response = result.get(0); - - assertThat(response.getPublishedDate()).isEqualTo(publishDate); } @Test @@ -160,22 +143,4 @@ public class ProblemSetSearchRepositoryCustomTest { assertThat(result).isEmpty(); } - @Test - void 컨펌된_문항세트_정확한_발행날짜_검증() { - // given - LocalDate publishDate = LocalDate.now().plusDays(1); - publishSaveService.createPublish(new PublishPostRequest(publishDate, 2L)); // 발행 처리 - - // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.confirmSearch( - "고3 모의고사", - null - ); - - // then - assertThat(result).hasSize(1); - ProblemSetSearchGetResponse response = result.get(0); - - assertThat(response.getPublishedDate()).isEqualTo(publishDate); - } } \ No newline at end of file From 4eae03e182c8debcccaf280fc777a5e8242374e0 Mon Sep 17 00:00:00 2001 From: HongGit <soojam@konkuk.ac.kr> Date: Fri, 21 Feb 2025 17:44:38 +0900 Subject: [PATCH 112/115] =?UTF-8?q?[hotfix]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EA=B0=9C=EB=B3=84=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C?= =?UTF-8?q?=20=EB=AC=B8=ED=95=AD=ED=83=80=EC=9D=B4=ED=8B=80=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problemset/dto/response/ProblemSummaryResponse.java | 6 +++--- .../domain/problemset/service/ProblemSetGetService.java | 4 +--- .../domain/problemset/service/ProblemSetGetServiceTest.java | 4 +--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java index dc6a197..036abb4 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -12,13 +12,13 @@ public record ProblemSummaryResponse( @NotNull(message = "문항 custom ID는 필수입니다") String problemCustomId, int number, - String practiceTestName, + String problemTitle, String memo, String mainProblemImageUrl, @NotNull(message = "컬렉션 값은 필수입니다.") List<String> tagNames ) { - public static ProblemSummaryResponse of(Problem problem, String practiceTestName, List<String> tagNames) { + public static ProblemSummaryResponse of(Problem problem, List<String> tagNames) { return ProblemSummaryResponse.builder() .problemId(problem.getId()) @@ -26,7 +26,7 @@ public static ProblemSummaryResponse of(Problem problem, String practiceTestName .number(problem.getNumber()) .memo(problem.getMemo()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) - .practiceTestName(practiceTestName) + .problemTitle(problem.getTitle()) .tagNames(tagNames) .build(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java index 2574719..31fcbda 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java @@ -25,7 +25,6 @@ public class ProblemSetGetService { private final ProblemSetRepository problemSetRepository; private final ProblemRepository problemRepository; - private final PracticeTestTagRepository practiceTestTagRepository; private final ConceptTagRepository conceptTagRepository; private final PublishRepository publishRepository; @@ -40,12 +39,11 @@ public ProblemSetGetResponse getProblemSet(Long problemSetId) { List<ProblemSummaryResponse> problemSummaries = new ArrayList<>(); for (Long problemId : problemSet.getProblemIds()) { Problem problem = problemRepository.findByIdElseThrow(problemId); - PracticeTestTag practiceTestTag = practiceTestTagRepository.findByIdElseThrow(problem.getPracticeTestId()); List<String> tagNames = conceptTagRepository.findAllByIdsElseThrow(problem.getConceptTagIds()) .stream() .map(ConceptTag::getName) .toList(); - problemSummaries.add(ProblemSummaryResponse.of(problem, practiceTestTag.getName(), tagNames)); + problemSummaries.add(ProblemSummaryResponse.of(problem, tagNames)); } return ProblemSetGetResponse.of(problemSet, publishedDates, problemSummaries); } diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java index e3c9a78..fbcefe0 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java @@ -58,7 +58,7 @@ void setUp() { assertThat(response.title()).isEqualTo("테스트 문항세트"); assertThat(response.problemSummaries()).hasSize(1); assertThat(response.problemSummaries().get(0).problemCustomId()).isEqualTo("1224052001"); - assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); + assertThat(response.problemSummaries().get(0).problemTitle()).isEqualTo("제목1"); assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); } @@ -89,12 +89,10 @@ void setUp() { // 첫 번째 문제 검증 assertThat(response.problemSummaries().get(0).problemCustomId()).isEqualTo("1224052001"); - assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); // 두 번째 문제 검증 assertThat(response.problemSummaries().get(1).problemCustomId()).isEqualTo("1224052002"); - assertThat(response.problemSummaries().get(1).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(1).tagNames()).contains("미분 개념", "삼각함수 개념"); } } \ No newline at end of file From f70d9809ee3f5ae88b82d3912eac04c4af2b040c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:14:07 +0900 Subject: [PATCH 113/115] =?UTF-8?q?[feat]=20=EB=AC=B8=ED=95=AD=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/ProblemSearchGetResponse.java | 19 +++++++++++-------- .../dto/response/ProblemSummaryResponse.java | 8 +++----- .../service/ProblemSetGetService.java | 4 +--- .../service/ProblemSetGetServiceTest.java | 3 --- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java index 80a1ecb..86a3ab0 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java @@ -2,6 +2,7 @@ import jakarta.validation.constraints.NotNull; import java.util.Set; +import java.util.stream.Collectors; import lombok.Getter; import lombok.NoArgsConstructor; @@ -9,24 +10,26 @@ @NoArgsConstructor public class ProblemSearchGetResponse { @NotNull(message = "문항 ID는 필수입니다") - private Long id; + private Long problemId; @NotNull(message = "문항 custom ID는 필수입니다") private String problemCustomId; - private String title; + private String problemTitle; private String memo; private String mainProblemImageUrl; @NotNull(message = "개념 태그리스트는 필수입니다") - private Set<ConceptTagSearchResponse> conceptTagResponses; + private Set<String> getTagName; - public ProblemSearchGetResponse(Long id, String problemCustomId, String title, String memo, + public ProblemSearchGetResponse(Long problemId, String problemCustomId, String problemTitle, String memo, String mainProblemImageUrl, - Set<ConceptTagSearchResponse> conceptTagResponses) { - this.id = id; + Set<ConceptTagSearchResponse> getTagName) { + this.problemId = problemId; this.problemCustomId = problemCustomId; - this.title = title; + this.problemTitle = problemTitle; this.memo = memo; this.mainProblemImageUrl = mainProblemImageUrl; - this.conceptTagResponses = conceptTagResponses; + this.getTagName = getTagName.stream() + .map(ConceptTagSearchResponse::getName) + .collect(Collectors.toSet()); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java index dc6a197..427ec6c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -11,22 +11,20 @@ public record ProblemSummaryResponse( Long problemId, @NotNull(message = "문항 custom ID는 필수입니다") String problemCustomId, - int number, - String practiceTestName, + String problemTitle, String memo, String mainProblemImageUrl, @NotNull(message = "컬렉션 값은 필수입니다.") List<String> tagNames ) { - public static ProblemSummaryResponse of(Problem problem, String practiceTestName, List<String> tagNames) { + public static ProblemSummaryResponse of(Problem problem, List<String> tagNames) { return ProblemSummaryResponse.builder() .problemId(problem.getId()) .problemCustomId(problem.getProblemCustomId()) - .number(problem.getNumber()) .memo(problem.getMemo()) + .problemTitle(problem.getTitle()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) - .practiceTestName(practiceTestName) .tagNames(tagNames) .build(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java index 2574719..8045874 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java @@ -2,7 +2,6 @@ import com.moplus.moplus_server.domain.concept.domain.ConceptTag; import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; -import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; @@ -40,12 +39,11 @@ public ProblemSetGetResponse getProblemSet(Long problemSetId) { List<ProblemSummaryResponse> problemSummaries = new ArrayList<>(); for (Long problemId : problemSet.getProblemIds()) { Problem problem = problemRepository.findByIdElseThrow(problemId); - PracticeTestTag practiceTestTag = practiceTestTagRepository.findByIdElseThrow(problem.getPracticeTestId()); List<String> tagNames = conceptTagRepository.findAllByIdsElseThrow(problem.getConceptTagIds()) .stream() .map(ConceptTag::getName) .toList(); - problemSummaries.add(ProblemSummaryResponse.of(problem, practiceTestTag.getName(), tagNames)); + problemSummaries.add(ProblemSummaryResponse.of(problem, tagNames)); } return ProblemSetGetResponse.of(problemSet, publishedDates, problemSummaries); } diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java index e3c9a78..f148055 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java @@ -58,7 +58,6 @@ void setUp() { assertThat(response.title()).isEqualTo("테스트 문항세트"); assertThat(response.problemSummaries()).hasSize(1); assertThat(response.problemSummaries().get(0).problemCustomId()).isEqualTo("1224052001"); - assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); } @@ -89,12 +88,10 @@ void setUp() { // 첫 번째 문제 검증 assertThat(response.problemSummaries().get(0).problemCustomId()).isEqualTo("1224052001"); - assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); // 두 번째 문제 검증 assertThat(response.problemSummaries().get(1).problemCustomId()).isEqualTo("1224052002"); - assertThat(response.problemSummaries().get(1).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(1).tagNames()).contains("미분 개념", "삼각함수 개념"); } } \ No newline at end of file From 8c93ef00a320b73ba37612f799ce4eded9742475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:18:43 +0900 Subject: [PATCH 114/115] =?UTF-8?q?[fix]=20merge=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problem/dto/response/ProblemSearchGetResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java index 86a3ab0..b2a47ef 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemSearchGetResponse.java @@ -17,17 +17,17 @@ public class ProblemSearchGetResponse { private String memo; private String mainProblemImageUrl; @NotNull(message = "개념 태그리스트는 필수입니다") - private Set<String> getTagName; + private Set<String> tagNames; public ProblemSearchGetResponse(Long problemId, String problemCustomId, String problemTitle, String memo, String mainProblemImageUrl, - Set<ConceptTagSearchResponse> getTagName) { + Set<ConceptTagSearchResponse> tagNames) { this.problemId = problemId; this.problemCustomId = problemCustomId; this.problemTitle = problemTitle; this.memo = memo; this.mainProblemImageUrl = mainProblemImageUrl; - this.getTagName = getTagName.stream() + this.tagNames = tagNames.stream() .map(ConceptTagSearchResponse::getName) .collect(Collectors.toSet()); } From b25513426ec4db9a94536ca9364775352cf8caa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Sun, 23 Feb 2025 14:14:31 +0900 Subject: [PATCH 115/115] Create README.md --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc7c1e8 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# 모플: 매일 3문항 수학 사고력 향상 서비스 + +매일 **기출 문제 3문항**을 풀며, **수학 사고력 취약점을 진단, 처방, 교정**까지 한 번에 해결하는 **올인원 수학 사고력 향상 서비스**입니다. + +--- + +## 🎯 사용자 서비스 기능 +<div style="display: flex; justify-content: center; gap: 10px;"> + <img src="https://github.com/user-attachments/assets/a4c8cf3c-72f9-4d99-b701-ff5f9cc5f85d" width="250" height="400"/> + <img src="https://github.com/user-attachments/assets/7704d134-8b45-4320-996c-5aa7deafffbd" width="250" height="400"/> + <img src="https://github.com/user-attachments/assets/0eb0bcb3-a400-43ae-8eda-773b09795d03" width="250" height="400"/> +</div> + +- **핵심 사고 과정 점검**: 각 기출 문항은 3개의 세부 문항으로 구성되어 있어, 취약점을 명확히 진단할 수 있습니다. +- **손해설 및 풀이 과정 제공**: 풀이 후, 문제 해결 과정에서 필요했던 핵심 사고력을 정리하여 개념 보완을 돕습니다. +- **오답 분석 및 맞춤 처방**: 세부 문항별 오답을 분석하고, 추가 학습이 필요한 개념을 처방합니다. +- **교정 학습 기능**: 틀린 문제와 유사한 문제를 다시 풀며, 취약한 사고 과정을 보완하고 사고력을 강화할 수 있습니다. + +--- + +## 🛠 어드민 서비스 기능 +<div style="display: flex; flex-direction: column; align-items: center; gap: 10px;"> + <img width="900" src="https://github.com/user-attachments/assets/c5007734-4f8c-4fb4-80fe-c46a98293798" /> + <img width="900" src="https://github.com/user-attachments/assets/eb8342d1-1ec9-46bb-a6b0-664f87eaca8e" /> + <img width="900" src="https://github.com/user-attachments/assets/90d4003d-4836-4408-9dd5-d698b57c2cab" /> +</div> + +- **문항 및 세부 문항 등록**: 새 문제와 세부 문항을 손쉽게 등록할 수 있습니다. +- **세트 구성 및 일정 관리**: 여러 문항을 세트로 묶어 원하는 날짜에 발행 가능합니다. +- **자동 배포 기능**: 발행된 세트는 사용자에게 **"오늘의 문제"**로 제공됩니다. + +--- + +## 🏗 Architecture +<div style="display: flex; justify-content: center;"> + <img width="900" src="https://github.com/user-attachments/assets/dae74b75-2c3f-436a-ac43-f09bcb1233ec" /> +</div> + +- 서비스의 **안정적인 운영을 위해 prod(운영)과 dev(개발) 서버를 분리**하여 배포 및 관리하고 있습니다. + +--- + +## 서버 팀원 소개 +|<img src="https://avatars.githubusercontent.com/u/74056843?v=4" width=200>|<img src="https://avatars.githubusercontent.com/u/101867059?v=4" width=200>| +|:--------:|:--------:| +|[BE 박세준](https://github.com/sejoon00)|[BE 홍석범](https://github.com/seokbeom00)| +</br>