From d7938815157fca0e6cbd7efcbd971d4669de503e Mon Sep 17 00:00:00 2001 From: TaeGang Kim <80620674+Pochomo@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:33:10 +0900 Subject: [PATCH 01/11] Create pr_template.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pr_template.md 생성, pr 컨벤션 적용 --- .github/PUL_REQUEST_TEMPLATE/pr_template.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/PUL_REQUEST_TEMPLATE/pr_template.md diff --git a/.github/PUL_REQUEST_TEMPLATE/pr_template.md b/.github/PUL_REQUEST_TEMPLATE/pr_template.md new file mode 100644 index 0000000..5eaa1e1 --- /dev/null +++ b/.github/PUL_REQUEST_TEMPLATE/pr_template.md @@ -0,0 +1,14 @@ +### PR 타입(하나 이상의 PR 타입을 선택해주세요) +-[] 기능 추가 +-[] 기능 삭제 +-[] 버그 수정 +-[] 의존성, 환경 변수, 빌드 관련 코드 업데이트 + +### 반영 브랜치 +ex) feat/login -> dev + +### 변경 사항 +ex) 로그인 시, 구글 소셜 로그인 기능을 추가했습니다. + +### 테스트 결과 +ex) 베이스 브랜치에 포함되기 위한 코드는 모두 정상적으로 동작해야 합니다. From 563e991d87ab13b52d528c5d5b3bb16ddd3c1562 Mon Sep 17 00:00:00 2001 From: TaeGang Kim <80620674+Pochomo@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:34:32 +0900 Subject: [PATCH 02/11] Delete .github/PUL_REQUEST_TEMPLATE directory --- .github/PUL_REQUEST_TEMPLATE/pr_template.md | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .github/PUL_REQUEST_TEMPLATE/pr_template.md diff --git a/.github/PUL_REQUEST_TEMPLATE/pr_template.md b/.github/PUL_REQUEST_TEMPLATE/pr_template.md deleted file mode 100644 index 5eaa1e1..0000000 --- a/.github/PUL_REQUEST_TEMPLATE/pr_template.md +++ /dev/null @@ -1,14 +0,0 @@ -### PR 타입(하나 이상의 PR 타입을 선택해주세요) --[] 기능 추가 --[] 기능 삭제 --[] 버그 수정 --[] 의존성, 환경 변수, 빌드 관련 코드 업데이트 - -### 반영 브랜치 -ex) feat/login -> dev - -### 변경 사항 -ex) 로그인 시, 구글 소셜 로그인 기능을 추가했습니다. - -### 테스트 결과 -ex) 베이스 브랜치에 포함되기 위한 코드는 모두 정상적으로 동작해야 합니다. From a2844b8a9e1081ba7b91f2c5e9efad8613e4b63b Mon Sep 17 00:00:00 2001 From: TaeGang Kim <80620674+Pochomo@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:36:12 +0900 Subject: [PATCH 03/11] Create pull_request_template.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pr 템플릿 적용 --- .github/pull_request_template.md | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..f42a6f5 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,35 @@ +## #️⃣ Issue Number + + +## 📝 요약(Summary) + + +## 🛠️ PR 유형 +어떤 변경 사항이 있나요? + +- [ ] 새로운 기능 추가 +- [ ] 버그 수정 +- [ ] CSS 등 사용자 UI 디자인 변경 +- [ ] 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경) +- [ ] 코드 리팩토링 +- [ ] 주석 추가 및 수정 +- [ ] 문서 수정 +- [ ] 테스트 추가, 테스트 리팩토링 +- [ ] 빌드 부분 혹은 패키지 매니저 수정 +- [ ] 파일 혹은 폴더명 수정 +- [ ] 파일 혹은 폴더 삭제 + +## 📸스크린샷 (선택) + +## 💬 공유사항 to 리뷰어 + + + + + +## ✅ PR Checklist + +PR이 다음 요구 사항을 충족하는지 확인하세요. + +- [ ] 커밋 메시지 컨벤션에 맞게 작성했습니다. +- [ ] 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트). From c8e2d24bc21f78ad457345301319c57d74bfc2f2 Mon Sep 17 00:00:00 2001 From: Sohyunxxi Date: Mon, 17 Mar 2025 15:24:05 +0900 Subject: [PATCH 04/11] feat: Add GroupedOpenApi for question and response APIs --- .../src/main/java/com/example/config/SwaggerConfig.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/resource-server/src/main/java/com/example/config/SwaggerConfig.java b/resource-server/src/main/java/com/example/config/SwaggerConfig.java index 661448a..1bf9fdc 100644 --- a/resource-server/src/main/java/com/example/config/SwaggerConfig.java +++ b/resource-server/src/main/java/com/example/config/SwaggerConfig.java @@ -119,4 +119,12 @@ public GroupedOpenApi getPlayerApi() { .pathsToMatch("/api/player/**", "/api/queue/**") .build(); } + + @Bean + public GroupedOpenApi getQuestionAPI(){ + return GroupedOpenApi.builder() + .group("질문 답변 관련") + .pathToMatch("/api/questions/**","/api/responses/**") + .build(); + } } From 18669d94ed635b73ceb6ff71fe6dc85ad64f9fe6 Mon Sep 17 00:00:00 2001 From: Sohyunxxi Date: Mon, 17 Mar 2025 15:48:13 +0900 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20=EC=B5=9C=EC=8B=A0=20develop=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20=EB=B0=8F=20GroupedOpenApi=20=EC=84=A4?= =?UTF-8?q?=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 --- .../src/main/java/com/example/config/SwaggerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource-server/src/main/java/com/example/config/SwaggerConfig.java b/resource-server/src/main/java/com/example/config/SwaggerConfig.java index 1bf9fdc..10b594a 100644 --- a/resource-server/src/main/java/com/example/config/SwaggerConfig.java +++ b/resource-server/src/main/java/com/example/config/SwaggerConfig.java @@ -123,7 +123,7 @@ public GroupedOpenApi getPlayerApi() { @Bean public GroupedOpenApi getQuestionAPI(){ return GroupedOpenApi.builder() - .group("질문 답변 관련") + .group("질문 및 답변 관련") .pathToMatch("/api/questions/**","/api/responses/**") .build(); } From 7504360c120275266d84c182b48e72df6c4db4cc Mon Sep 17 00:00:00 2001 From: Sohyunxxi Date: Mon, 17 Mar 2025 17:01:46 +0900 Subject: [PATCH 06/11] fix: Update SwaggerConfig path matching method --- .../src/main/java/com/example/config/SwaggerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource-server/src/main/java/com/example/config/SwaggerConfig.java b/resource-server/src/main/java/com/example/config/SwaggerConfig.java index 10b594a..9c66b7d 100644 --- a/resource-server/src/main/java/com/example/config/SwaggerConfig.java +++ b/resource-server/src/main/java/com/example/config/SwaggerConfig.java @@ -124,7 +124,7 @@ public GroupedOpenApi getPlayerApi() { public GroupedOpenApi getQuestionAPI(){ return GroupedOpenApi.builder() .group("질문 및 답변 관련") - .pathToMatch("/api/questions/**","/api/responses/**") + .pathsToMatch("/api/questions/**","/api/responses/**") .build(); } } From 35e4d30c713495092ea9f34c5bfc775c54dc3b8c Mon Sep 17 00:00:00 2001 From: Sohyunxxi Date: Wed, 19 Mar 2025 18:21:00 +0900 Subject: [PATCH 07/11] feat: add QnA and Test Progress APIs with Swagger documentation --- .../java/com/example/web/QnAController.java | 75 ++++++++++++++++ .../example/web/TestProgressController.java | 87 +++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 resource-server/src/main/java/com/example/web/QnAController.java create mode 100644 resource-server/src/main/java/com/example/web/TestProgressController.java diff --git a/resource-server/src/main/java/com/example/web/QnAController.java b/resource-server/src/main/java/com/example/web/QnAController.java new file mode 100644 index 0000000..0897f93 --- /dev/null +++ b/resource-server/src/main/java/com/example/web/QnAController.java @@ -0,0 +1,75 @@ +package com.example.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.Random; + +@RestController +@RequestMapping("/qna") +public class QnAController { + + @Autowired + private QuestionRepository questionRepository; + + @Autowired + private AnswerRepository answerRepository; + + // 랜덤 질문 제공 API + @Operation( + summary = "랜덤 질문 가져오기", + description = "데이터베이스에서 무작위로 선택된 질문을 제공합니다." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "성공적으로 랜덤 질문 반환", + content = @Content(schema = @Schema(implementation = Question.class))), + @ApiResponse(responseCode = "404", description = "질문 데이터 없음", + content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"질문 데이터가 없습니다.\"}"))) + }) + @GetMapping("/questions/random") + public ResponseEntity getRandomQuestion() { + List questions = questionRepository.findAll(); + if (questions.isEmpty()) { + return ResponseEntity.status(404).body(Map.of( + "status", 404, + "message", "질문 데이터가 없습니다." + )); + } + Question randomQuestion = questions.get(new Random().nextInt(questions.size())); + return ResponseEntity.ok(randomQuestion); + } + + // 답변 저장 API + @Operation( + summary = "답변 저장하기", + description = "사용자가 작성한 답변을 저장합니다." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "답변 저장 완료", + content = @Content(schema = @Schema(implementation = Answer.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청", + content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}"))) + }) + @PostMapping("/answers") + public ResponseEntity saveAnswer( + @RequestBody Answer answer + ) { + if (answer == null || answer.getContent() == null || answer.getContent().isEmpty()) { + return ResponseEntity.badRequest().body(Map.of( + "status", 400, + "message", "잘못된 요청입니다. 답변 내용을 입력해주세요." + )); + } + Answer savedAnswer = answerRepository.save(answer); + return ResponseEntity.ok(savedAnswer); + } +} diff --git a/resource-server/src/main/java/com/example/web/TestProgressController.java b/resource-server/src/main/java/com/example/web/TestProgressController.java new file mode 100644 index 0000000..ecd2141 --- /dev/null +++ b/resource-server/src/main/java/com/example/web/TestProgressController.java @@ -0,0 +1,87 @@ +package com.example.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; +import java.util.Optional; + +@RestController +@RequestMapping("/api") +public class QnAController { + + @Autowired + private TestProgressRepository testProgressRepository; + + @Autowired + private ParticipantRepository participantRepository; + + // 현재 테스트 진행 상황 조회 API + @Operation( + summary = "현재 테스트 진행 상황 조회", + description = "사용자가 테스트를 어디까지 진행했는지 조회합니다." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "진행 상황 반환", + content = @Content(schema = @Schema(implementation = TestProgress.class))), + @ApiResponse(responseCode = "404", description = "진행 상황 없음", + content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"진행 상황을 찾을 수 없습니다.\"}"))) + }) + @GetMapping("/test-progress/{userId}") + public ResponseEntity getTestProgress(@PathVariable Long userId) { + Optional progress = testProgressRepository.findByUserId(userId); + return progress.map(ResponseEntity::ok).orElseGet(() -> + ResponseEntity.status(404).body(Map.of( + "status", 404, + "message", "진행 상황을 찾을 수 없습니다." + )) + ); + } + + // 현재 테스트 진행 상황 저장 API + @Operation( + summary = "현재 테스트 진행 상황 저장", + description = "사용자의 테스트 진행 상황을 저장합니다." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "진행 상황 저장 완료", + content = @Content(schema = @Schema(implementation = TestProgress.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청", + content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}"))) + }) + @PostMapping("/test-progress") + public ResponseEntity saveTestProgress(@RequestBody TestProgress testProgress) { + if (testProgress == null || testProgress.getUserId() == null) { + return ResponseEntity.badRequest().body(Map.of( + "status", 400, + "message", "잘못된 요청입니다. 사용자 ID가 필요합니다." + )); + } + TestProgress savedProgress = testProgressRepository.save(testProgress); + return ResponseEntity.ok(savedProgress); + } + + // 참여 인원 카운팅 API + @Operation( + summary = "참여 인원 카운팅", + description = "이 테스트에 총 몇 명이 참여했는지 반환합니다." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "참여 인원 수 반환", + content = @Content(schema = @Schema(type = "integer", format = "int64"))) + }) + @GetMapping("/participants/count") + public ResponseEntity> getParticipantCount() { + long count = participantRepository.count(); + return ResponseEntity.ok(Map.of( + "status", 200, + "participantCount", count + )); + } +} From dae4e79149fba5c395a3f4560f0552de727b3923 Mon Sep 17 00:00:00 2001 From: Sohyunxxi Date: Wed, 19 Mar 2025 18:57:01 +0900 Subject: [PATCH 08/11] feat: Add Swagger group for test progress related APIs --- .../src/main/java/com/example/config/SwaggerConfig.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/resource-server/src/main/java/com/example/config/SwaggerConfig.java b/resource-server/src/main/java/com/example/config/SwaggerConfig.java index 9c66b7d..ffd344c 100644 --- a/resource-server/src/main/java/com/example/config/SwaggerConfig.java +++ b/resource-server/src/main/java/com/example/config/SwaggerConfig.java @@ -127,4 +127,12 @@ public GroupedOpenApi getQuestionAPI(){ .pathsToMatch("/api/questions/**","/api/responses/**") .build(); } + + @Bean + public GroupedOpenApi getProgressAPI(){ + return GroupedOpenApi.builder() + .group("테스트 진행상황 관련") + .pathsToMatch("/api/progress/**") + .build(); + } } From 4bcb2b7bec791c4803da46c477d7372dfc30d32d Mon Sep 17 00:00:00 2001 From: Sohyunxxi Date: Wed, 19 Mar 2025 19:11:23 +0900 Subject: [PATCH 09/11] refactor: Refactor QnAController to handle only question and answer related APIs --- .../com/example/config/SwaggerConfig.java | 11 +++++----- .../java/com/example/web/QnAController.java | 21 ++++++++----------- .../example/web/TestProgressController.java | 16 +++++++------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/resource-server/src/main/java/com/example/config/SwaggerConfig.java b/resource-server/src/main/java/com/example/config/SwaggerConfig.java index ffd344c..76101f9 100644 --- a/resource-server/src/main/java/com/example/config/SwaggerConfig.java +++ b/resource-server/src/main/java/com/example/config/SwaggerConfig.java @@ -121,18 +121,19 @@ public GroupedOpenApi getPlayerApi() { } @Bean - public GroupedOpenApi getQuestionAPI(){ + public GroupedOpenApi getQnAAPI() { return GroupedOpenApi.builder() .group("질문 및 답변 관련") - .pathsToMatch("/api/questions/**","/api/responses/**") + .pathsToMatch("/api/questions/**", "/api/responses/**") .build(); } @Bean - public GroupedOpenApi getProgressAPI(){ + public GroupedOpenApi getTestProgressAPI() { return GroupedOpenApi.builder() - .group("테스트 진행상황 관련") - .pathsToMatch("/api/progress/**") + .group("진행 상황 및 참여 인원 관련") + .pathsToMatch("/api/test-progress/**", "/api/participants/**") .build(); } + } diff --git a/resource-server/src/main/java/com/example/web/QnAController.java b/resource-server/src/main/java/com/example/web/QnAController.java index 0897f93..38ca693 100644 --- a/resource-server/src/main/java/com/example/web/QnAController.java +++ b/resource-server/src/main/java/com/example/web/QnAController.java @@ -1,7 +1,6 @@ package com.example.web; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.media.Content; @@ -15,9 +14,9 @@ import java.util.Random; @RestController -@RequestMapping("/qna") +@RequestMapping("/api/questions") // 질문 관련 API 경로 public class QnAController { - + @Autowired private QuestionRepository questionRepository; @@ -31,11 +30,11 @@ public class QnAController { ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "성공적으로 랜덤 질문 반환", - content = @Content(schema = @Schema(implementation = Question.class))), + content = @Content(schema = @Schema(implementation = Question.class)))), @ApiResponse(responseCode = "404", description = "질문 데이터 없음", - content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"질문 데이터가 없습니다.\"}"))) + content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"질문 데이터가 없습니다.\"}")))) }) - @GetMapping("/questions/random") + @GetMapping("/") // /questions 경로로 수정 public ResponseEntity getRandomQuestion() { List questions = questionRepository.findAll(); if (questions.isEmpty()) { @@ -55,14 +54,12 @@ public ResponseEntity getRandomQuestion() { ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "답변 저장 완료", - content = @Content(schema = @Schema(implementation = Answer.class))), + content = @Content(schema = @Schema(implementation = Answer.class)))), @ApiResponse(responseCode = "400", description = "잘못된 요청", - content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}"))) + content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}")))) }) - @PostMapping("/answers") - public ResponseEntity saveAnswer( - @RequestBody Answer answer - ) { + @PostMapping("/responses") // /responses 경로로 수정 + public ResponseEntity saveAnswer(@RequestBody Answer answer) { if (answer == null || answer.getContent() == null || answer.getContent().isEmpty()) { return ResponseEntity.badRequest().body(Map.of( "status", 400, diff --git a/resource-server/src/main/java/com/example/web/TestProgressController.java b/resource-server/src/main/java/com/example/web/TestProgressController.java index ecd2141..124f17d 100644 --- a/resource-server/src/main/java/com/example/web/TestProgressController.java +++ b/resource-server/src/main/java/com/example/web/TestProgressController.java @@ -13,9 +13,9 @@ import java.util.Optional; @RestController -@RequestMapping("/api") -public class QnAController { - +@RequestMapping("/api/test-progress") // 진행 상황 관련 API 경로 +public class TestProgressController { + @Autowired private TestProgressRepository testProgressRepository; @@ -31,9 +31,9 @@ public class QnAController { @ApiResponse(responseCode = "200", description = "진행 상황 반환", content = @Content(schema = @Schema(implementation = TestProgress.class))), @ApiResponse(responseCode = "404", description = "진행 상황 없음", - content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"진행 상황을 찾을 수 없습니다.\"}"))) + content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"진행 상황을 찾을 수 없습니다.\"}")))) }) - @GetMapping("/test-progress/{userId}") + @GetMapping("/{userId}") public ResponseEntity getTestProgress(@PathVariable Long userId) { Optional progress = testProgressRepository.findByUserId(userId); return progress.map(ResponseEntity::ok).orElseGet(() -> @@ -53,9 +53,9 @@ public ResponseEntity getTestProgress(@PathVariable Long userId) { @ApiResponse(responseCode = "200", description = "진행 상황 저장 완료", content = @Content(schema = @Schema(implementation = TestProgress.class))), @ApiResponse(responseCode = "400", description = "잘못된 요청", - content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}"))) + content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}")))) }) - @PostMapping("/test-progress") + @PostMapping("/") public ResponseEntity saveTestProgress(@RequestBody TestProgress testProgress) { if (testProgress == null || testProgress.getUserId() == null) { return ResponseEntity.badRequest().body(Map.of( @@ -74,7 +74,7 @@ public ResponseEntity saveTestProgress(@RequestBody TestProgress testPro ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "참여 인원 수 반환", - content = @Content(schema = @Schema(type = "integer", format = "int64"))) + content = @Content(schema = @Schema(type = "integer", format = "int64")))) }) @GetMapping("/participants/count") public ResponseEntity> getParticipantCount() { From e6d98a3d34242181f0077335fe84b305f4b67763 Mon Sep 17 00:00:00 2001 From: Sohyunxxi Date: Wed, 26 Mar 2025 14:56:36 +0900 Subject: [PATCH 10/11] Cleanup: Remove all progress-related API --- .../java/com/example/web/QnAController.java | 8 ++-- .../example/web/TestProgressController.java | 48 +------------------ 2 files changed, 5 insertions(+), 51 deletions(-) diff --git a/resource-server/src/main/java/com/example/web/QnAController.java b/resource-server/src/main/java/com/example/web/QnAController.java index 38ca693..bbd5d83 100644 --- a/resource-server/src/main/java/com/example/web/QnAController.java +++ b/resource-server/src/main/java/com/example/web/QnAController.java @@ -30,9 +30,9 @@ public class QnAController { ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "성공적으로 랜덤 질문 반환", - content = @Content(schema = @Schema(implementation = Question.class)))), + content = @Content(schema = @Schema(implementation = Question.class))), @ApiResponse(responseCode = "404", description = "질문 데이터 없음", - content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"질문 데이터가 없습니다.\"}")))) + content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"질문 데이터가 없습니다.\"}"))) }) @GetMapping("/") // /questions 경로로 수정 public ResponseEntity getRandomQuestion() { @@ -54,9 +54,9 @@ public ResponseEntity getRandomQuestion() { ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "답변 저장 완료", - content = @Content(schema = @Schema(implementation = Answer.class)))), + content = @Content(schema = @Schema(implementation = Answer.class))), @ApiResponse(responseCode = "400", description = "잘못된 요청", - content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}")))) + content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}"))) }) @PostMapping("/responses") // /responses 경로로 수정 public ResponseEntity saveAnswer(@RequestBody Answer answer) { diff --git a/resource-server/src/main/java/com/example/web/TestProgressController.java b/resource-server/src/main/java/com/example/web/TestProgressController.java index 124f17d..e718173 100644 --- a/resource-server/src/main/java/com/example/web/TestProgressController.java +++ b/resource-server/src/main/java/com/example/web/TestProgressController.java @@ -22,59 +22,13 @@ public class TestProgressController { @Autowired private ParticipantRepository participantRepository; - // 현재 테스트 진행 상황 조회 API - @Operation( - summary = "현재 테스트 진행 상황 조회", - description = "사용자가 테스트를 어디까지 진행했는지 조회합니다." - ) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "진행 상황 반환", - content = @Content(schema = @Schema(implementation = TestProgress.class))), - @ApiResponse(responseCode = "404", description = "진행 상황 없음", - content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"진행 상황을 찾을 수 없습니다.\"}")))) - }) - @GetMapping("/{userId}") - public ResponseEntity getTestProgress(@PathVariable Long userId) { - Optional progress = testProgressRepository.findByUserId(userId); - return progress.map(ResponseEntity::ok).orElseGet(() -> - ResponseEntity.status(404).body(Map.of( - "status", 404, - "message", "진행 상황을 찾을 수 없습니다." - )) - ); - } - - // 현재 테스트 진행 상황 저장 API - @Operation( - summary = "현재 테스트 진행 상황 저장", - description = "사용자의 테스트 진행 상황을 저장합니다." - ) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "진행 상황 저장 완료", - content = @Content(schema = @Schema(implementation = TestProgress.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청", - content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}")))) - }) - @PostMapping("/") - public ResponseEntity saveTestProgress(@RequestBody TestProgress testProgress) { - if (testProgress == null || testProgress.getUserId() == null) { - return ResponseEntity.badRequest().body(Map.of( - "status", 400, - "message", "잘못된 요청입니다. 사용자 ID가 필요합니다." - )); - } - TestProgress savedProgress = testProgressRepository.save(testProgress); - return ResponseEntity.ok(savedProgress); - } - - // 참여 인원 카운팅 API @Operation( summary = "참여 인원 카운팅", description = "이 테스트에 총 몇 명이 참여했는지 반환합니다." ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "참여 인원 수 반환", - content = @Content(schema = @Schema(type = "integer", format = "int64")))) + content = @Content(schema = @Schema(type = "integer", format = "int64"))) }) @GetMapping("/participants/count") public ResponseEntity> getParticipantCount() { From c7fcf41432c57d6000982ae10df225bca01d60a7 Mon Sep 17 00:00:00 2001 From: Sohyunxxi Date: Wed, 26 Mar 2025 18:51:05 +0900 Subject: [PATCH 11/11] feat(qna, progress): add DTOs, domain models, repositories, and use cases --- .../java/com/example/web/QnAController.java | 37 +++++++-------- .../example/web/TestProgressController.java | 24 ++++------ .../web/progress/domain/Participant.java | 47 +++++++++++++++++++ .../web/progress/domain/TestProgress.java | 46 ++++++++++++++++++ .../web/progress/dto/ParticipantCountDTO.java | 23 +++++++++ .../repository/ParticipantRepository.java | 8 ++++ .../repository/TestProgressRepository.java | 8 ++++ .../progress/usecase/TestProgressService.java | 16 +++++++ .../com/example/web/qna/domain/Answer.java | 23 +++++++++ .../com/example/web/qna/domain/Question.java | 20 ++++++++ .../com/example/web/qna/dto/AnswerDTO.java | 25 ++++++++++ .../com/example/web/qna/dto/QuestionDTO.java | 19 ++++++++ .../web/qna/repository/AnswerRepository.java | 10 ++++ .../qna/repository/QuestionRepository.java | 10 ++++ .../example/web/qna/usecase/QnAService.java | 36 ++++++++++++++ 15 files changed, 317 insertions(+), 35 deletions(-) create mode 100644 resource-server/src/main/java/com/example/web/progress/domain/Participant.java create mode 100644 resource-server/src/main/java/com/example/web/progress/domain/TestProgress.java create mode 100644 resource-server/src/main/java/com/example/web/progress/dto/ParticipantCountDTO.java create mode 100644 resource-server/src/main/java/com/example/web/progress/repository/ParticipantRepository.java create mode 100644 resource-server/src/main/java/com/example/web/progress/repository/TestProgressRepository.java create mode 100644 resource-server/src/main/java/com/example/web/progress/usecase/TestProgressService.java create mode 100644 resource-server/src/main/java/com/example/web/qna/domain/Answer.java create mode 100644 resource-server/src/main/java/com/example/web/qna/domain/Question.java create mode 100644 resource-server/src/main/java/com/example/web/qna/dto/AnswerDTO.java create mode 100644 resource-server/src/main/java/com/example/web/qna/dto/QuestionDTO.java create mode 100644 resource-server/src/main/java/com/example/web/qna/repository/AnswerRepository.java create mode 100644 resource-server/src/main/java/com/example/web/qna/repository/QuestionRepository.java create mode 100644 resource-server/src/main/java/com/example/web/qna/usecase/QnAService.java diff --git a/resource-server/src/main/java/com/example/web/QnAController.java b/resource-server/src/main/java/com/example/web/QnAController.java index bbd5d83..e278a9d 100644 --- a/resource-server/src/main/java/com/example/web/QnAController.java +++ b/resource-server/src/main/java/com/example/web/QnAController.java @@ -1,5 +1,10 @@ package com.example.web; +import com.example.web.qna.domain.Question; +import com.example.web.qna.domain.Answer; +import com.example.web.qna.dto.AnswerDTO; +import com.example.web.qna.dto.QuestionDTO; +import com.example.web.qna.usecase.QnAService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -9,56 +14,48 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.List; import java.util.Map; -import java.util.Random; @RestController -@RequestMapping("/api/questions") // 질문 관련 API 경로 +@RequestMapping("/api/questions") public class QnAController { @Autowired - private QuestionRepository questionRepository; - - @Autowired - private AnswerRepository answerRepository; + private QnAService qnAService; - // 랜덤 질문 제공 API @Operation( summary = "랜덤 질문 가져오기", description = "데이터베이스에서 무작위로 선택된 질문을 제공합니다." ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "성공적으로 랜덤 질문 반환", - content = @Content(schema = @Schema(implementation = Question.class))), + content = @Content(schema = @Schema(implementation = QuestionDTO.class))), @ApiResponse(responseCode = "404", description = "질문 데이터 없음", - content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"질문 데이터가 없습니다.\"}"))) + content = @Content(schema = @Schema(example = "{\"status\": 404, \"message\": \"질문 데이터가 없습니다.\"}"))), }) - @GetMapping("/") // /questions 경로로 수정 + @GetMapping("/") public ResponseEntity getRandomQuestion() { - List questions = questionRepository.findAll(); - if (questions.isEmpty()) { + QuestionDTO question = qnAService.getRandomQuestion(); + if (question == null) { return ResponseEntity.status(404).body(Map.of( "status", 404, "message", "질문 데이터가 없습니다." )); } - Question randomQuestion = questions.get(new Random().nextInt(questions.size())); - return ResponseEntity.ok(randomQuestion); + return ResponseEntity.ok(question); } - // 답변 저장 API @Operation( summary = "답변 저장하기", description = "사용자가 작성한 답변을 저장합니다." ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "답변 저장 완료", - content = @Content(schema = @Schema(implementation = Answer.class))), + content = @Content(schema = @Schema(implementation = AnswerDTO.class))), @ApiResponse(responseCode = "400", description = "잘못된 요청", - content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}"))) + content = @Content(schema = @Schema(example = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}"))), }) - @PostMapping("/responses") // /responses 경로로 수정 + @PostMapping("/responses") public ResponseEntity saveAnswer(@RequestBody Answer answer) { if (answer == null || answer.getContent() == null || answer.getContent().isEmpty()) { return ResponseEntity.badRequest().body(Map.of( @@ -66,7 +63,7 @@ public ResponseEntity saveAnswer(@RequestBody Answer answer) { "message", "잘못된 요청입니다. 답변 내용을 입력해주세요." )); } - Answer savedAnswer = answerRepository.save(answer); + AnswerDTO savedAnswer = qnAService.saveAnswer(answer); return ResponseEntity.ok(savedAnswer); } } diff --git a/resource-server/src/main/java/com/example/web/TestProgressController.java b/resource-server/src/main/java/com/example/web/TestProgressController.java index e718173..c6e1b4b 100644 --- a/resource-server/src/main/java/com/example/web/TestProgressController.java +++ b/resource-server/src/main/java/com/example/web/TestProgressController.java @@ -1,5 +1,7 @@ package com.example.web; +import com.example.web.progress.dto.ParticipantCountDTO; +import com.example.web.progress.usecase.TestProgressService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -9,18 +11,12 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.Map; -import java.util.Optional; - @RestController -@RequestMapping("/api/test-progress") // 진행 상황 관련 API 경로 +@RequestMapping("/api/test-progress") public class TestProgressController { @Autowired - private TestProgressRepository testProgressRepository; - - @Autowired - private ParticipantRepository participantRepository; + private TestProgressService testProgressService; @Operation( summary = "참여 인원 카운팅", @@ -28,14 +24,12 @@ public class TestProgressController { ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "참여 인원 수 반환", - content = @Content(schema = @Schema(type = "integer", format = "int64"))) + content = @Content(schema = @Schema(implementation = ParticipantCountDTO.class))) }) @GetMapping("/participants/count") - public ResponseEntity> getParticipantCount() { - long count = participantRepository.count(); - return ResponseEntity.ok(Map.of( - "status", 200, - "participantCount", count - )); + public ResponseEntity getParticipantCount() { + long count = testProgressService.getParticipantCount(); + ParticipantCountDTO response = new ParticipantCountDTO((int) count); + return ResponseEntity.ok(response); } } diff --git a/resource-server/src/main/java/com/example/web/progress/domain/Participant.java b/resource-server/src/main/java/com/example/web/progress/domain/Participant.java new file mode 100644 index 0000000..ff2ff70 --- /dev/null +++ b/resource-server/src/main/java/com/example/web/progress/domain/Participant.java @@ -0,0 +1,47 @@ +package com.example.web.progress.domain; + +import jakarta.persistence.*; + +@Entity +public class Participant { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String participantName; + + @ManyToOne + @JoinColumn(name = "test_progress_id") + private TestProgress testProgress; + + // 기본 생성자 + public Participant() {} + + // 생성자 + public Participant(String participantName, TestProgress testProgress) { + this.participantName = participantName; + this.testProgress = testProgress; + } + + // Getter 및 Setter + public Long getId() { + return id; + } + + public String getParticipantName() { + return participantName; + } + + public void setParticipantName(String participantName) { + this.participantName = participantName; + } + + public TestProgress getTestProgress() { + return testProgress; + } + + public void setTestProgress(TestProgress testProgress) { + this.testProgress = testProgress; + } +} diff --git a/resource-server/src/main/java/com/example/web/progress/domain/TestProgress.java b/resource-server/src/main/java/com/example/web/progress/domain/TestProgress.java new file mode 100644 index 0000000..efd0972 --- /dev/null +++ b/resource-server/src/main/java/com/example/web/progress/domain/TestProgress.java @@ -0,0 +1,46 @@ +package com.example.web.progress.domain; + +import jakarta.persistence.*; +import java.util.List; + +@Entity +public class TestProgress { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String testName; + + @OneToMany(mappedBy = "testProgress") + private List participants; + + // 기본 생성자 + public TestProgress() {} + + // 생성자 + public TestProgress(String testName) { + this.testName = testName; + } + + // Getter 및 Setter + public Long getId() { + return id; + } + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } + + public List getParticipants() { + return participants; + } + + public void setParticipants(List participants) { + this.participants = participants; + } +} diff --git a/resource-server/src/main/java/com/example/web/progress/dto/ParticipantCountDTO.java b/resource-server/src/main/java/com/example/web/progress/dto/ParticipantCountDTO.java new file mode 100644 index 0000000..567539e --- /dev/null +++ b/resource-server/src/main/java/com/example/web/progress/dto/ParticipantCountDTO.java @@ -0,0 +1,23 @@ +package com.example.web.progress.dto; + +public class ParticipantCountDTO { + + private int participantCount; + + // 기본 생성자 + public ParticipantCountDTO() {} + + // 생성자 + public ParticipantCountDTO(int participantCount) { + this.participantCount = participantCount; + } + + // Getter 및 Setter + public int getParticipantCount() { + return participantCount; + } + + public void setParticipantCount(int participantCount) { + this.participantCount = participantCount; + } +} diff --git a/resource-server/src/main/java/com/example/web/progress/repository/ParticipantRepository.java b/resource-server/src/main/java/com/example/web/progress/repository/ParticipantRepository.java new file mode 100644 index 0000000..62b951b --- /dev/null +++ b/resource-server/src/main/java/com/example/web/progress/repository/ParticipantRepository.java @@ -0,0 +1,8 @@ +package com.example.web.progress.repository; + +import com.example.web.progress.domain.Participant; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ParticipantRepository extends JpaRepository { + // 추가적인 쿼리 메서드가 필요하면 여기에 정의합니다. +} diff --git a/resource-server/src/main/java/com/example/web/progress/repository/TestProgressRepository.java b/resource-server/src/main/java/com/example/web/progress/repository/TestProgressRepository.java new file mode 100644 index 0000000..325aa0e --- /dev/null +++ b/resource-server/src/main/java/com/example/web/progress/repository/TestProgressRepository.java @@ -0,0 +1,8 @@ +package com.example.web.progress.repository; + +import com.example.web.progress.domain.TestProgress; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TestProgressRepository extends JpaRepository { + // 추가적인 쿼리 메서드가 필요하면 여기에 정의합니다. +} diff --git a/resource-server/src/main/java/com/example/web/progress/usecase/TestProgressService.java b/resource-server/src/main/java/com/example/web/progress/usecase/TestProgressService.java new file mode 100644 index 0000000..a0d621c --- /dev/null +++ b/resource-server/src/main/java/com/example/web/progress/usecase/TestProgressService.java @@ -0,0 +1,16 @@ +package com.example.web.progress.usecase; + +import com.example.web.progress.repository.ParticipantRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class TestProgressService { + + @Autowired + private ParticipantRepository participantRepository; + + public long getParticipantCount() { + return participantRepository.count(); + } +} diff --git a/resource-server/src/main/java/com/example/web/qna/domain/Answer.java b/resource-server/src/main/java/com/example/web/qna/domain/Answer.java new file mode 100644 index 0000000..a5db67a --- /dev/null +++ b/resource-server/src/main/java/com/example/web/qna/domain/Answer.java @@ -0,0 +1,23 @@ +package com.example.web.qna.domain; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Table(name = "answers") +public class Answer { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String content; + + @ManyToOne + @JoinColumn(name = "question_id", nullable = false) + private Question question; +} diff --git a/resource-server/src/main/java/com/example/web/qna/domain/Question.java b/resource-server/src/main/java/com/example/web/qna/domain/Question.java new file mode 100644 index 0000000..11184e0 --- /dev/null +++ b/resource-server/src/main/java/com/example/web/qna/domain/Question.java @@ -0,0 +1,20 @@ +package com.example.web.qna.domain; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Table(name = "questions") +public class Question { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String content; + +} diff --git a/resource-server/src/main/java/com/example/web/qna/dto/AnswerDTO.java b/resource-server/src/main/java/com/example/web/qna/dto/AnswerDTO.java new file mode 100644 index 0000000..5abffaf --- /dev/null +++ b/resource-server/src/main/java/com/example/web/qna/dto/AnswerDTO.java @@ -0,0 +1,25 @@ +package com.example.web.qna.dto; + +public class AnswerDTO { + private Long id; + private String content; + private Long questionId; + + public AnswerDTO(Long id, String content, Long questionId) { + this.id = id; + this.content = content; + this.questionId = questionId; + } + + public Long getId() { + return id; + } + + public String getContent() { + return content; + } + + public Long getQuestionId() { + return questionId; + } +} diff --git a/resource-server/src/main/java/com/example/web/qna/dto/QuestionDTO.java b/resource-server/src/main/java/com/example/web/qna/dto/QuestionDTO.java new file mode 100644 index 0000000..628e1db --- /dev/null +++ b/resource-server/src/main/java/com/example/web/qna/dto/QuestionDTO.java @@ -0,0 +1,19 @@ +package com.example.web.qna.dto; + +public class QuestionDTO { + private Long id; + private String content; + + public QuestionDTO(Long id, String content) { + this.id = id; + this.content = content; + } + + public Long getId() { + return id; + } + + public String getContent() { + return content; + } +} diff --git a/resource-server/src/main/java/com/example/web/qna/repository/AnswerRepository.java b/resource-server/src/main/java/com/example/web/qna/repository/AnswerRepository.java new file mode 100644 index 0000000..91b8161 --- /dev/null +++ b/resource-server/src/main/java/com/example/web/qna/repository/AnswerRepository.java @@ -0,0 +1,10 @@ +package com.example.web.qna.repository; + +import com.example.web.qna.domain.Answer; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AnswerRepository extends JpaRepository { + +} diff --git a/resource-server/src/main/java/com/example/web/qna/repository/QuestionRepository.java b/resource-server/src/main/java/com/example/web/qna/repository/QuestionRepository.java new file mode 100644 index 0000000..b149bec --- /dev/null +++ b/resource-server/src/main/java/com/example/web/qna/repository/QuestionRepository.java @@ -0,0 +1,10 @@ +package com.example.web.qna.repository; + +import com.example.web.qna.domain.Question; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface QuestionRepository extends JpaRepository { + +} diff --git a/resource-server/src/main/java/com/example/web/qna/usecase/QnAService.java b/resource-server/src/main/java/com/example/web/qna/usecase/QnAService.java new file mode 100644 index 0000000..a3ad4d2 --- /dev/null +++ b/resource-server/src/main/java/com/example/web/qna/usecase/QnAService.java @@ -0,0 +1,36 @@ +package com.example.web.qna.usecase; + +import com.example.web.qna.domain.Answer; +import com.example.web.qna.domain.Question; +import com.example.web.qna.dto.AnswerDTO; +import com.example.web.qna.dto.QuestionDTO; +import com.example.web.qna.repository.AnswerRepository; +import com.example.web.qna.repository.QuestionRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class QnAService { + + @Autowired + private QuestionRepository questionRepository; + + @Autowired + private AnswerRepository answerRepository; + + public QuestionDTO getRandomQuestion() { + List questions = questionRepository.findAll(); + if (questions.isEmpty()) { + return null; + } + Question randomQuestion = questions.get((int) (Math.random() * questions.size())); + return new QuestionDTO(randomQuestion.getId(), randomQuestion.getContent()); + } + + public AnswerDTO saveAnswer(Answer answer) { + Answer savedAnswer = answerRepository.save(answer); + return new AnswerDTO(savedAnswer.getId(), savedAnswer.getContent(), savedAnswer.getQuestion().getId()); + } +}