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 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] =?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 6/9] 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 7/9] 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 8/9] 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 9/9] 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() {