From 3b0f10f5bdc90119645eac88f74738769de98d47 Mon Sep 17 00:00:00 2001 From: Jung-kr Date: Thu, 3 Jul 2025 09:44:43 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20admin=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=EC=9D=98=20ProblemRequest,=20ProblemResponse=20dto=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20AdminProblemRequest,=20AdminProblemRespons?= =?UTF-8?q?e=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 --- .../backend/admin/controller/AdminController.java | 7 +++---- ...roblemRequest.java => AdminProblemRequest.java} | 4 +--- ...blemResponse.java => AdminProblemResponse.java} | 6 +++--- .../backend/admin/service/AdminProblemService.java | 14 +++++++------- .../problem/service/ProblemFindService.java | 14 ++++++++++++++ .../backend/user/controller/UserController.java | 4 ++-- 6 files changed, 30 insertions(+), 19 deletions(-) rename src/main/java/dev/codehouse/backend/admin/dto/{ProblemRequest.java => AdminProblemRequest.java} (79%) rename src/main/java/dev/codehouse/backend/admin/dto/{ProblemResponse.java => AdminProblemResponse.java} (80%) create mode 100644 src/main/java/dev/codehouse/backend/problem/service/ProblemFindService.java diff --git a/src/main/java/dev/codehouse/backend/admin/controller/AdminController.java b/src/main/java/dev/codehouse/backend/admin/controller/AdminController.java index d0c0e18..5f44245 100644 --- a/src/main/java/dev/codehouse/backend/admin/controller/AdminController.java +++ b/src/main/java/dev/codehouse/backend/admin/controller/AdminController.java @@ -8,7 +8,6 @@ import dev.codehouse.backend.global.response.ApiResponseFactory; import dev.codehouse.backend.global.response.ResponseCode; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -40,13 +39,13 @@ public ResponseEntity> adjustPoint(@RequestBody U } @PostMapping("/problem") - public ResponseEntity> register(@RequestBody ProblemRequest request) { + public ResponseEntity> register(@RequestBody AdminProblemRequest request) { problemService.saveProblem(request); return ApiResponseFactory.success(ResponseCode.PROBLEM_REGISTERED); } @GetMapping("/problem/{day}") - public ResponseEntity>> getByDay(@PathVariable String day) { + public ResponseEntity>> getByDay(@PathVariable String day) { return ApiResponseFactory.success(ResponseCode.PROBLEM_FOUND, problemService.getProblems(day)); } @@ -57,7 +56,7 @@ public ResponseEntity> deleteProblem(@PathVariable String numb } @GetMapping("/problem") - public ResponseEntity>> getAllProblemsPaged(@RequestParam(defaultValue = "0") int page) { + public ResponseEntity>> getAllProblemsPaged(@RequestParam(defaultValue = "0") int page) { // return ApiResponseFactory.success(ResponseCode.PROBLEM_FOUND, problemService.getAllProblemsPaged(page)); return ApiResponseFactory.success(ResponseCode.PROBLEM_FOUND, problemService.getAllProblems()); } diff --git a/src/main/java/dev/codehouse/backend/admin/dto/ProblemRequest.java b/src/main/java/dev/codehouse/backend/admin/dto/AdminProblemRequest.java similarity index 79% rename from src/main/java/dev/codehouse/backend/admin/dto/ProblemRequest.java rename to src/main/java/dev/codehouse/backend/admin/dto/AdminProblemRequest.java index 8d2b82f..1e16734 100644 --- a/src/main/java/dev/codehouse/backend/admin/dto/ProblemRequest.java +++ b/src/main/java/dev/codehouse/backend/admin/dto/AdminProblemRequest.java @@ -2,10 +2,8 @@ import lombok.Getter; -import java.time.LocalDateTime; - @Getter -public class ProblemRequest { +public class AdminProblemRequest { private String title; private String problemNumber; private String url; diff --git a/src/main/java/dev/codehouse/backend/admin/dto/ProblemResponse.java b/src/main/java/dev/codehouse/backend/admin/dto/AdminProblemResponse.java similarity index 80% rename from src/main/java/dev/codehouse/backend/admin/dto/ProblemResponse.java rename to src/main/java/dev/codehouse/backend/admin/dto/AdminProblemResponse.java index b43f031..5b8852d 100644 --- a/src/main/java/dev/codehouse/backend/admin/dto/ProblemResponse.java +++ b/src/main/java/dev/codehouse/backend/admin/dto/AdminProblemResponse.java @@ -6,7 +6,7 @@ @AllArgsConstructor @Getter -public class ProblemResponse { +public class AdminProblemResponse { private String title; private String problemNumber; private String url; @@ -14,8 +14,8 @@ public class ProblemResponse { private int point; private String day; - public static ProblemResponse from(Problem problem) { - return new ProblemResponse( + public static AdminProblemResponse from(Problem problem) { + return new AdminProblemResponse( problem.getTitle(), problem.getProblemNumber(), problem.getUrl(), diff --git a/src/main/java/dev/codehouse/backend/admin/service/AdminProblemService.java b/src/main/java/dev/codehouse/backend/admin/service/AdminProblemService.java index 78e037b..8e28087 100644 --- a/src/main/java/dev/codehouse/backend/admin/service/AdminProblemService.java +++ b/src/main/java/dev/codehouse/backend/admin/service/AdminProblemService.java @@ -1,7 +1,7 @@ package dev.codehouse.backend.admin.service; -import dev.codehouse.backend.admin.dto.ProblemRequest; -import dev.codehouse.backend.admin.dto.ProblemResponse; +import dev.codehouse.backend.admin.dto.AdminProblemRequest; +import dev.codehouse.backend.admin.dto.AdminProblemResponse; import dev.codehouse.backend.problem.entity.Problem; import dev.codehouse.backend.admin.repository.ProblemRepository; import dev.codehouse.backend.global.exception.AdminException; @@ -19,7 +19,7 @@ public class AdminProblemService { private final ProblemRepository problemRepository; //문제 등록 - public void saveProblem(ProblemRequest dto) { + public void saveProblem(AdminProblemRequest dto) { if (problemRepository.findByProblemNumber(dto.getProblemNumber()).isPresent()) { throw new AdminException(ResponseCode.PROBLEM_ALREADY_EXISTS); } @@ -36,17 +36,17 @@ public void deleteProblem(String problemNumber) { } //날짜로 문제 조회 - public List getProblems(String day) { + public List getProblems(String day) { List problems = problemRepository.findByDay(day); return problems.stream() - .map(ProblemResponse::from) + .map(AdminProblemResponse::from) .toList(); } - public List getAllProblems() { + public List getAllProblems() { List problems = problemRepository.findAll(); return problems.stream() - .map(ProblemResponse::from) + .map(AdminProblemResponse::from) .toList(); } diff --git a/src/main/java/dev/codehouse/backend/problem/service/ProblemFindService.java b/src/main/java/dev/codehouse/backend/problem/service/ProblemFindService.java new file mode 100644 index 0000000..a6fc572 --- /dev/null +++ b/src/main/java/dev/codehouse/backend/problem/service/ProblemFindService.java @@ -0,0 +1,14 @@ +package dev.codehouse.backend.problem.service; + +import dev.codehouse.backend.admin.repository.ProblemRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ProblemFindService { + + private final ProblemRepository problemRepository; + + +} diff --git a/src/main/java/dev/codehouse/backend/user/controller/UserController.java b/src/main/java/dev/codehouse/backend/user/controller/UserController.java index d86859e..d020a41 100644 --- a/src/main/java/dev/codehouse/backend/user/controller/UserController.java +++ b/src/main/java/dev/codehouse/backend/user/controller/UserController.java @@ -50,12 +50,12 @@ public Map getPoint(@PathVariable String username) { return Map.of("point", user.getPoint()); } - @GetMapping("/toprank/all") + @GetMapping("/ranking") public ResponseEntity>> getTopRanking(){ return ApiResponseFactory.success(ResponseCode.RANK_FOUND,userRankingService.getTopRanking()); } - @GetMapping("/toprank/class/{className}") + @GetMapping("/ranking/{className}") public ResponseEntity>> getClassRanking(@PathVariable String className){ return ApiResponseFactory.success(ResponseCode.RANK_FOUND,userRankingService.getclassRanking(className)); } From 554ea61025a6e0a665ad2028d8b363ca0b318caf Mon Sep 17 00:00:00 2001 From: Jung-kr Date: Thu, 3 Jul 2025 12:50:49 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=EB=AC=B8=EC=A0=9C=20=EC=A0=9C?= =?UTF-8?q?=EC=B6=9C=20=EC=B2=98=EB=A6=AC=20=EC=8B=9C=20=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=81=EB=A6=BD=20=EB=B0=8F=20=EC=A0=95=EC=82=B0?= =?UTF-8?q?=20=EB=82=B4=EC=97=AD=20=EB=B0=98=EC=98=81=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자 포인트 증가 및 History 저장 로직을 problemSubmitService 내부로 리팩토링 - 이미 제출한 문제 중복 처리 방지 - 응답 코드 ResponseCode.PROBLEM_SOLVED 적용 --- .../admin/{entity => domain}/Notice.java | 2 +- .../admin/dto/AdminProblemResponse.java | 2 +- .../backend/admin/dto/NoticeResponse.java | 2 +- .../admin/repository/NoticeRepository.java | 2 +- .../admin/service/AdminNoticeService.java | 2 +- .../admin/service/AdminProblemService.java | 4 +- .../backend/global/config/SecurityConfig.java | 2 +- .../exception/ExternalApiException.java | 9 +++ .../global/exception/ProblemException.java | 9 +++ .../backend/global/response/ResponseCode.java | 6 ++ .../problem/controller/ProblemController.java | 37 +++++----- .../problem/{entity => domain}/Problem.java | 2 +- .../backend/problem/dto/ProblemResponse.java | 27 +++++++ .../problem/dto/ProblemSubmitRequest.java | 10 +++ .../repository/ProblemRepository.java | 4 +- .../problem/service/ProblemCheckService.java | 71 +++++-------------- .../problem/service/ProblemFindService.java | 26 ++++++- .../problem/service/ProblemSubmitService.java | 56 +++++++++++++++ .../UserSolvedCheckerController.java | 41 ----------- .../service/UserSolvedCheckerService.java | 40 ----------- 20 files changed, 190 insertions(+), 164 deletions(-) rename src/main/java/dev/codehouse/backend/admin/{entity => domain}/Notice.java (96%) create mode 100644 src/main/java/dev/codehouse/backend/global/exception/ExternalApiException.java create mode 100644 src/main/java/dev/codehouse/backend/global/exception/ProblemException.java rename src/main/java/dev/codehouse/backend/problem/{entity => domain}/Problem.java (95%) create mode 100644 src/main/java/dev/codehouse/backend/problem/dto/ProblemResponse.java create mode 100644 src/main/java/dev/codehouse/backend/problem/dto/ProblemSubmitRequest.java rename src/main/java/dev/codehouse/backend/{admin => problem}/repository/ProblemRepository.java (78%) create mode 100644 src/main/java/dev/codehouse/backend/problem/service/ProblemSubmitService.java delete mode 100644 src/main/java/dev/codehouse/backend/user/controller/UserSolvedCheckerController.java delete mode 100644 src/main/java/dev/codehouse/backend/user/service/UserSolvedCheckerService.java diff --git a/src/main/java/dev/codehouse/backend/admin/entity/Notice.java b/src/main/java/dev/codehouse/backend/admin/domain/Notice.java similarity index 96% rename from src/main/java/dev/codehouse/backend/admin/entity/Notice.java rename to src/main/java/dev/codehouse/backend/admin/domain/Notice.java index 2927aa7..0c5267f 100644 --- a/src/main/java/dev/codehouse/backend/admin/entity/Notice.java +++ b/src/main/java/dev/codehouse/backend/admin/domain/Notice.java @@ -1,4 +1,4 @@ -package dev.codehouse.backend.admin.entity; +package dev.codehouse.backend.admin.domain; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/dev/codehouse/backend/admin/dto/AdminProblemResponse.java b/src/main/java/dev/codehouse/backend/admin/dto/AdminProblemResponse.java index 5b8852d..a586fe3 100644 --- a/src/main/java/dev/codehouse/backend/admin/dto/AdminProblemResponse.java +++ b/src/main/java/dev/codehouse/backend/admin/dto/AdminProblemResponse.java @@ -1,6 +1,6 @@ package dev.codehouse.backend.admin.dto; -import dev.codehouse.backend.problem.entity.Problem; +import dev.codehouse.backend.problem.domain.Problem; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/dev/codehouse/backend/admin/dto/NoticeResponse.java b/src/main/java/dev/codehouse/backend/admin/dto/NoticeResponse.java index 04dc34b..e7f38df 100644 --- a/src/main/java/dev/codehouse/backend/admin/dto/NoticeResponse.java +++ b/src/main/java/dev/codehouse/backend/admin/dto/NoticeResponse.java @@ -1,6 +1,6 @@ package dev.codehouse.backend.admin.dto; -import dev.codehouse.backend.admin.entity.Notice; +import dev.codehouse.backend.admin.domain.Notice; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/dev/codehouse/backend/admin/repository/NoticeRepository.java b/src/main/java/dev/codehouse/backend/admin/repository/NoticeRepository.java index fdac36b..8ccc4da 100644 --- a/src/main/java/dev/codehouse/backend/admin/repository/NoticeRepository.java +++ b/src/main/java/dev/codehouse/backend/admin/repository/NoticeRepository.java @@ -1,6 +1,6 @@ package dev.codehouse.backend.admin.repository; -import dev.codehouse.backend.admin.entity.Notice; +import dev.codehouse.backend.admin.domain.Notice; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/dev/codehouse/backend/admin/service/AdminNoticeService.java b/src/main/java/dev/codehouse/backend/admin/service/AdminNoticeService.java index 3ab67b8..db23655 100644 --- a/src/main/java/dev/codehouse/backend/admin/service/AdminNoticeService.java +++ b/src/main/java/dev/codehouse/backend/admin/service/AdminNoticeService.java @@ -2,7 +2,7 @@ import dev.codehouse.backend.admin.dto.NoticeRequest; import dev.codehouse.backend.admin.dto.NoticeResponse; -import dev.codehouse.backend.admin.entity.Notice; +import dev.codehouse.backend.admin.domain.Notice; import dev.codehouse.backend.admin.repository.NoticeRepository; import dev.codehouse.backend.global.exception.AdminException; import dev.codehouse.backend.global.response.ResponseCode; diff --git a/src/main/java/dev/codehouse/backend/admin/service/AdminProblemService.java b/src/main/java/dev/codehouse/backend/admin/service/AdminProblemService.java index 8e28087..87dec0d 100644 --- a/src/main/java/dev/codehouse/backend/admin/service/AdminProblemService.java +++ b/src/main/java/dev/codehouse/backend/admin/service/AdminProblemService.java @@ -2,8 +2,8 @@ import dev.codehouse.backend.admin.dto.AdminProblemRequest; import dev.codehouse.backend.admin.dto.AdminProblemResponse; -import dev.codehouse.backend.problem.entity.Problem; -import dev.codehouse.backend.admin.repository.ProblemRepository; +import dev.codehouse.backend.problem.domain.Problem; +import dev.codehouse.backend.problem.repository.ProblemRepository; import dev.codehouse.backend.global.exception.AdminException; import dev.codehouse.backend.global.response.ResponseCode; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/dev/codehouse/backend/global/config/SecurityConfig.java b/src/main/java/dev/codehouse/backend/global/config/SecurityConfig.java index db20995..095c731 100644 --- a/src/main/java/dev/codehouse/backend/global/config/SecurityConfig.java +++ b/src/main/java/dev/codehouse/backend/global/config/SecurityConfig.java @@ -34,7 +34,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(HttpMethod.GET, "/api/admin/notice").permitAll() .requestMatchers( "/api/auth/**", - "/api/user/toprank/**", + "/api/user/ranking/**", "/api/chat/**", "/ws/**" ).permitAll() diff --git a/src/main/java/dev/codehouse/backend/global/exception/ExternalApiException.java b/src/main/java/dev/codehouse/backend/global/exception/ExternalApiException.java new file mode 100644 index 0000000..a9af438 --- /dev/null +++ b/src/main/java/dev/codehouse/backend/global/exception/ExternalApiException.java @@ -0,0 +1,9 @@ +package dev.codehouse.backend.global.exception; + +import dev.codehouse.backend.global.response.ResponseCode; + +public class ExternalApiException extends BaseException { + public ExternalApiException(ResponseCode code) { + super(code); + } +} diff --git a/src/main/java/dev/codehouse/backend/global/exception/ProblemException.java b/src/main/java/dev/codehouse/backend/global/exception/ProblemException.java new file mode 100644 index 0000000..e3981de --- /dev/null +++ b/src/main/java/dev/codehouse/backend/global/exception/ProblemException.java @@ -0,0 +1,9 @@ +package dev.codehouse.backend.global.exception; + +import dev.codehouse.backend.global.response.ResponseCode; + +public class ProblemException extends BaseException { + public ProblemException(ResponseCode code) { + super(code); + } +} diff --git a/src/main/java/dev/codehouse/backend/global/response/ResponseCode.java b/src/main/java/dev/codehouse/backend/global/response/ResponseCode.java index 3d9d5a1..ad2e640 100644 --- a/src/main/java/dev/codehouse/backend/global/response/ResponseCode.java +++ b/src/main/java/dev/codehouse/backend/global/response/ResponseCode.java @@ -13,6 +13,8 @@ public enum ResponseCode { INVALID_REQUEST(HttpStatus.BAD_REQUEST, "요청 형식이 올바르지 않습니다."), DUPLICATE_USERNAME(HttpStatus.BAD_REQUEST, "이미 존재하는 사용자입니다"), INVALID_PASSWORD(HttpStatus.BAD_REQUEST,"비밀번호가 일치하지 않습니다"), + PROBLEM_NOT_SOLVED(HttpStatus.BAD_REQUEST, "문제를 아직 해결하지 않았습니다."), + PROBLEM_NOT_TODAY(HttpStatus.BAD_REQUEST, "오늘의 문제가 아닙니다."), //401 Unauthorized USER_NOT_FOUND(HttpStatus.UNAUTHORIZED, "존재하지 않는 아이디입니다."), @@ -21,6 +23,9 @@ public enum ResponseCode { PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 문제가 존재하지 않습니다."), CLASS_NOT_FOUND(HttpStatus.NOT_FOUND, "회차 정보가 올바르지 않습니다"), + //409 Conflict + PROBLEM_ALREADY_SOLVED(HttpStatus.CONFLICT, "이미 해결한 문제입니다."), + //500 Internal Server Error DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "데이터베이스 오류가 발생했습니다"), EXTERNAL_API_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "외부 API 호출 중 오류가 발생했습니다."), @@ -39,6 +44,7 @@ public enum ResponseCode { USER_FOUND(HttpStatus.OK, "유저 조회 성공"), RANK_FOUND(HttpStatus.OK, "랭킹 조회 성공"), HISTORY_FOUND(HttpStatus.OK, "정산내역 조회 성공"), + PROBLEM_SOLVED(HttpStatus.OK, "문제 해결 및 포인트 적립 완료"), //201 NOTICE_CREATED(HttpStatus.CREATED, "초기 공지사항이 생성되었습니다."), diff --git a/src/main/java/dev/codehouse/backend/problem/controller/ProblemController.java b/src/main/java/dev/codehouse/backend/problem/controller/ProblemController.java index 6e0748a..9ce5ea4 100644 --- a/src/main/java/dev/codehouse/backend/problem/controller/ProblemController.java +++ b/src/main/java/dev/codehouse/backend/problem/controller/ProblemController.java @@ -1,31 +1,36 @@ package dev.codehouse.backend.problem.controller; +import dev.codehouse.backend.global.response.ApiResponse; +import dev.codehouse.backend.global.response.ApiResponseFactory; +import dev.codehouse.backend.global.response.ResponseCode; +import dev.codehouse.backend.problem.dto.ProblemResponse; +import dev.codehouse.backend.problem.dto.ProblemSubmitRequest; import dev.codehouse.backend.problem.service.ProblemCheckService; +import dev.codehouse.backend.problem.service.ProblemFindService; +import dev.codehouse.backend.problem.service.ProblemSubmitService; import lombok.RequiredArgsConstructor; -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; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; import java.util.List; @RestController -@RequestMapping("/api/solvedCheck") +@RequestMapping("/api/problem") @RequiredArgsConstructor public class ProblemController { - private final ProblemCheckService problemCheckService; - @GetMapping("/check") - public boolean checkSolved( - @RequestParam("user") String userId, - @RequestParam("problem") int problemId) { - return problemCheckService.hasUserSolvedProblem(userId, problemId); + private final ProblemSubmitService problemSubmitService; + private final ProblemFindService problemFindService; + + @PostMapping("/check") + public ResponseEntity> submitProblems(@RequestBody ProblemSubmitRequest request, Authentication authentication) { + problemSubmitService.submitSolvedProblem(authentication.getName(), request.getProblemNumber(), request.getPoint()); + return ApiResponseFactory.success(ResponseCode.PROBLEM_SOLVED); } - @GetMapping("/list") - public List getProblemList( - @RequestParam("user") String userId, - @RequestParam(value = "size", defaultValue = "20") int size) { - return problemCheckService.getProblemList(userId, size); + @GetMapping("/{today}") + public ResponseEntity>> getProblems(@PathVariable String today, Authentication authentication) { + return ApiResponseFactory.success(ResponseCode.PROBLEM_FOUND, problemFindService.getProblems(today, authentication.getName())); } } diff --git a/src/main/java/dev/codehouse/backend/problem/entity/Problem.java b/src/main/java/dev/codehouse/backend/problem/domain/Problem.java similarity index 95% rename from src/main/java/dev/codehouse/backend/problem/entity/Problem.java rename to src/main/java/dev/codehouse/backend/problem/domain/Problem.java index d553dfd..4b609e2 100644 --- a/src/main/java/dev/codehouse/backend/problem/entity/Problem.java +++ b/src/main/java/dev/codehouse/backend/problem/domain/Problem.java @@ -1,4 +1,4 @@ -package dev.codehouse.backend.problem.entity; +package dev.codehouse.backend.problem.domain; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/dev/codehouse/backend/problem/dto/ProblemResponse.java b/src/main/java/dev/codehouse/backend/problem/dto/ProblemResponse.java new file mode 100644 index 0000000..0003e3e --- /dev/null +++ b/src/main/java/dev/codehouse/backend/problem/dto/ProblemResponse.java @@ -0,0 +1,27 @@ +package dev.codehouse.backend.problem.dto; + +import dev.codehouse.backend.problem.domain.Problem; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class ProblemResponse { + private String title; + private String problemNumber; + private String url; + private String difficulty; + private int points; + private boolean solved; + + public static ProblemResponse from(Problem problem, boolean solved) { + return new ProblemResponse( + problem.getTitle(), + problem.getProblemNumber(), + problem.getUrl(), + problem.getDifficulty(), + problem.getPoint(), + solved + ); + } +} diff --git a/src/main/java/dev/codehouse/backend/problem/dto/ProblemSubmitRequest.java b/src/main/java/dev/codehouse/backend/problem/dto/ProblemSubmitRequest.java new file mode 100644 index 0000000..12a3dc7 --- /dev/null +++ b/src/main/java/dev/codehouse/backend/problem/dto/ProblemSubmitRequest.java @@ -0,0 +1,10 @@ +package dev.codehouse.backend.problem.dto; + +import lombok.Getter; + +@Getter +public class ProblemSubmitRequest { + + private String problemNumber; + private int point; +} diff --git a/src/main/java/dev/codehouse/backend/admin/repository/ProblemRepository.java b/src/main/java/dev/codehouse/backend/problem/repository/ProblemRepository.java similarity index 78% rename from src/main/java/dev/codehouse/backend/admin/repository/ProblemRepository.java rename to src/main/java/dev/codehouse/backend/problem/repository/ProblemRepository.java index 47a2eab..cdd37fa 100644 --- a/src/main/java/dev/codehouse/backend/admin/repository/ProblemRepository.java +++ b/src/main/java/dev/codehouse/backend/problem/repository/ProblemRepository.java @@ -1,6 +1,6 @@ -package dev.codehouse.backend.admin.repository; +package dev.codehouse.backend.problem.repository; -import dev.codehouse.backend.problem.entity.Problem; +import dev.codehouse.backend.problem.domain.Problem; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/dev/codehouse/backend/problem/service/ProblemCheckService.java b/src/main/java/dev/codehouse/backend/problem/service/ProblemCheckService.java index 3ac4ebb..489cf5a 100644 --- a/src/main/java/dev/codehouse/backend/problem/service/ProblemCheckService.java +++ b/src/main/java/dev/codehouse/backend/problem/service/ProblemCheckService.java @@ -2,9 +2,18 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import dev.codehouse.backend.global.exception.ExternalApiException; +import dev.codehouse.backend.global.exception.ProblemException; +import dev.codehouse.backend.global.exception.UserException; +import dev.codehouse.backend.global.response.ResponseCode; +import dev.codehouse.backend.problem.domain.Problem; +import dev.codehouse.backend.problem.repository.ProblemRepository; +import dev.codehouse.backend.user.domain.User; +import dev.codehouse.backend.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; @@ -14,21 +23,17 @@ import java.util.ArrayList; import java.util.List; +import static dev.codehouse.backend.global.response.ResponseCode.PROBLEM_NOT_FOUND; +import static dev.codehouse.backend.global.response.ResponseCode.USER_NOT_FOUND; + @Service @RequiredArgsConstructor -@Slf4j public class ProblemCheckService { + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); - private final ObjectMapper objectMapper; public static final String BASE_URL = "https://solved.ac/api/v3"; - /** - * UserId 를 가진 사용자가 ProblemId 문제를 풀었는지 반환하는 함수 - * @param userId 사용자 - * @param problemId 문제 번호 - * @return 풀었으면 true, 아니면 false - */ - public boolean hasUserSolvedProblem(String userId, int problemId) { + public boolean checkSolvedProblem(String userId, int problemId) { try { URI uri = UriComponentsBuilder .fromUriString(BASE_URL + "/search/problem") @@ -45,8 +50,7 @@ public boolean hasUserSolvedProblem(String userId, int problemId) { HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { - log.warn("Solved.ac 응답 실패: status={}", response.statusCode()); - return false; + throw new ExternalApiException(ResponseCode.EXTERNAL_API_ERROR); } ObjectMapper objectMapper = new ObjectMapper(); @@ -61,52 +65,9 @@ public boolean hasUserSolvedProblem(String userId, int problemId) { } } } - return false; } catch (Exception e) { - throw new RuntimeException("External API Error: Failed to check problem status", e); - } - } - - /** - * UserId 를 가진 사용자가 풀었던 문제를 List 형태로 반환 - * @param userId 사용자 - * @param size 문제 개수 (기본 20, 최대 50) - * @return 문제 번호 List - */ - public List getProblemList(String userId, int size) { - - List problemIds = new ArrayList<>(); - int page = 1; - - try { - while (problemIds.size() < size) { - URI uri = UriComponentsBuilder - .fromUriString(BASE_URL + "/search/problem") - .queryParam("query", "solved_by:" + userId) - .queryParam("sort", "level") - .queryParam("direction", "desc") - .queryParam("page", page++) - .build() - .toUri(); - - HttpRequest request = HttpRequest.newBuilder(uri).GET().build(); - HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() != 200) break; - - JsonNode items = objectMapper.readTree(response.body()).get("items"); - if (items == null || items.isEmpty()) break; - - for (JsonNode item : items) { - problemIds.add(item.get("problemId").asInt()); - if (problemIds.size() >= size) break; - } - } - } catch (Exception e) { - throw new RuntimeException("푼 문제 번호 조회 실패", e); + throw new ExternalApiException(ResponseCode.EXTERNAL_API_ERROR); } - - return problemIds; } } diff --git a/src/main/java/dev/codehouse/backend/problem/service/ProblemFindService.java b/src/main/java/dev/codehouse/backend/problem/service/ProblemFindService.java index a6fc572..fa33d9d 100644 --- a/src/main/java/dev/codehouse/backend/problem/service/ProblemFindService.java +++ b/src/main/java/dev/codehouse/backend/problem/service/ProblemFindService.java @@ -1,14 +1,38 @@ package dev.codehouse.backend.problem.service; -import dev.codehouse.backend.admin.repository.ProblemRepository; +import dev.codehouse.backend.global.exception.UserException; +import dev.codehouse.backend.global.response.ResponseCode; +import dev.codehouse.backend.problem.repository.ProblemRepository; +import dev.codehouse.backend.problem.dto.ProblemResponse; +import dev.codehouse.backend.problem.domain.Problem; +import dev.codehouse.backend.user.domain.User; +import dev.codehouse.backend.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + @Service @RequiredArgsConstructor public class ProblemFindService { private final ProblemRepository problemRepository; + private final UserRepository userRepository; + public List getProblems(String day, String username) { + List problems = problemRepository.findByDay(day); + User user = userRepository.findByUsername(username).orElseThrow( + () -> new UserException(ResponseCode.USER_NOT_FOUND) + ); + Set solvedProblems = new HashSet<>(user.getSolvedProblems()); + return problems.stream() + .map(problem -> ProblemResponse.from( + problem, + solvedProblems.contains(problem.getProblemNumber()) + )) + .toList(); + } } diff --git a/src/main/java/dev/codehouse/backend/problem/service/ProblemSubmitService.java b/src/main/java/dev/codehouse/backend/problem/service/ProblemSubmitService.java new file mode 100644 index 0000000..e9b4596 --- /dev/null +++ b/src/main/java/dev/codehouse/backend/problem/service/ProblemSubmitService.java @@ -0,0 +1,56 @@ +package dev.codehouse.backend.problem.service; + +import dev.codehouse.backend.global.exception.ProblemException; +import dev.codehouse.backend.global.exception.UserException; +import dev.codehouse.backend.global.response.ResponseCode; +import dev.codehouse.backend.problem.domain.Problem; +import dev.codehouse.backend.problem.repository.ProblemRepository; +import dev.codehouse.backend.user.domain.User; +import dev.codehouse.backend.user.domain.UserHistory; +import dev.codehouse.backend.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; + +import static dev.codehouse.backend.global.response.ResponseCode.PROBLEM_NOT_FOUND; +import static dev.codehouse.backend.global.response.ResponseCode.USER_NOT_FOUND; + +@Service +@RequiredArgsConstructor +public class ProblemSubmitService { + + private final UserRepository userRepository; + private final ProblemRepository problemRepository; + private final ProblemCheckService problemCheckService; + + @Transactional + public void submitSolvedProblem(String username, String problemNo, int point) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UserException(USER_NOT_FOUND)); + Problem problem = problemRepository.findByProblemNumber(problemNo) + .orElseThrow(() -> new ProblemException(PROBLEM_NOT_FOUND)); + + String today = LocalDate.now().toString(); + if (!problem.getDay().equals(today)) { + throw new ProblemException(ResponseCode.PROBLEM_NOT_TODAY); + } + + if (user.hasSolvedProblem(problemNo)) { + throw new ProblemException(ResponseCode.PROBLEM_ALREADY_SOLVED); + } + + boolean isSolved = problemCheckService.checkSolvedProblem(username, Integer.parseInt(problemNo)); + if (!isSolved) { + throw new ProblemException(ResponseCode.PROBLEM_NOT_SOLVED); + } + + user.addSolvedProblem(problemNo); + user.adjustPoint(point); + + String reason = "문제" + problemNo + "번 풀어서 적립"; + user.getHistories().add(UserHistory.problemSolved(username, reason, point)); + userRepository.save(user); + } +} diff --git a/src/main/java/dev/codehouse/backend/user/controller/UserSolvedCheckerController.java b/src/main/java/dev/codehouse/backend/user/controller/UserSolvedCheckerController.java deleted file mode 100644 index 420c5b3..0000000 --- a/src/main/java/dev/codehouse/backend/user/controller/UserSolvedCheckerController.java +++ /dev/null @@ -1,41 +0,0 @@ -package dev.codehouse.backend.user.controller; - -import dev.codehouse.backend.user.service.UserSolvedCheckerService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/users") -public class UserSolvedCheckerController { - private final UserSolvedCheckerService userSolvedCheckerService; - - @PostMapping("{username}/solved/{problemNo}") - public ResponseEntity markSolvedProblem( - @PathVariable String username, - @PathVariable String problemNo - ) { - userSolvedCheckerService.markUserSolvedProblem(username, problemNo); - return ResponseEntity.ok().build(); - } - - @GetMapping("/{username}/solved/{problemNo}") - public ResponseEntity checkSolvedProblem( - @PathVariable String username, - @PathVariable String problemNo - ) { - boolean solved = userSolvedCheckerService.isProblemSolved(username, problemNo); - return ResponseEntity.ok(solved); - } - - @GetMapping("/{username}/solved") - public ResponseEntity> getSolvedProblems( - @PathVariable String username - ) { - List problems = userSolvedCheckerService.getSolvedProblems(username); - return ResponseEntity.ok(problems); - } -} diff --git a/src/main/java/dev/codehouse/backend/user/service/UserSolvedCheckerService.java b/src/main/java/dev/codehouse/backend/user/service/UserSolvedCheckerService.java deleted file mode 100644 index 0c0bb92..0000000 --- a/src/main/java/dev/codehouse/backend/user/service/UserSolvedCheckerService.java +++ /dev/null @@ -1,40 +0,0 @@ -package dev.codehouse.backend.user.service; - -import dev.codehouse.backend.user.domain.User; -import dev.codehouse.backend.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class UserSolvedCheckerService { - - private final UserRepository userRepository; - - public void markUserSolvedProblem(String username, String problemNo) { - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new IllegalArgumentException("유저가 존재하지 않습니다")); - - if (user.hasSolvedProblem(problemNo)) { - throw new IllegalStateException("이미 푼 문제입니다"); - } - user.addSolvedProblem(problemNo); - userRepository.save(user); - } - - public boolean isProblemSolved(String username, String problemNo) { - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new IllegalArgumentException("유저가 존재하지 않습니다")); - - return user.hasSolvedProblem(problemNo); - } - - public List getSolvedProblems(String username) { - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new IllegalArgumentException("유저가 존재하지 않습니다")); - - return user.getSolvedProblems(); - } -}