diff --git a/src/main/java/com/codzilla/backend/Leaderboard/LeaderboardService.java b/src/main/java/com/codzilla/backend/Leaderboard/LeaderboardService.java index 5129a61..f588f1e 100644 --- a/src/main/java/com/codzilla/backend/Leaderboard/LeaderboardService.java +++ b/src/main/java/com/codzilla/backend/Leaderboard/LeaderboardService.java @@ -37,22 +37,39 @@ public LeaderboardResponseDTO getLeaderboard(String currentEmail) { int total = (int) userRepository.count(); List topUsers = - userRepository.findAllByOrderByRatingDescIdAsc(PageRequest.of(0, EDGE_SIZE)); + userRepository.findAllByOrderByRatingDescIdAsc(PageRequest.of( + 0, + EDGE_SIZE + )); List bottomUsers; if (total <= EDGE_SIZE) { bottomUsers = List.of(); } else { - int bottomCount = Math.min(EDGE_SIZE, total - EDGE_SIZE); + int bottomCount = Math.min( + EDGE_SIZE, + total - EDGE_SIZE + ); List bottomAsc = - userRepository.findAllByOrderByRatingAscIdDesc(PageRequest.of(0, bottomCount)); + userRepository.findAllByOrderByRatingAscIdDesc(PageRequest.of( + 0, + bottomCount + )); bottomUsers = new ArrayList<>(bottomAsc); Collections.reverse(bottomUsers); } - List top = mapWithRank(topUsers, 1, currentEmail); + List top = mapWithRank( + topUsers, + 1, + currentEmail + ); int bottomStartRank = total - bottomUsers.size() + 1; - List bottom = mapWithRank(bottomUsers, bottomStartRank, currentEmail); + List bottom = mapWithRank( + bottomUsers, + bottomStartRank, + currentEmail + ); User me = userRepository.findByEmail(currentEmail).orElse(null); LeaderboardEntryDTO currentUser = null; @@ -60,14 +77,24 @@ public LeaderboardResponseDTO getLeaderboard(String currentEmail) { long above = userRepository.countByRatingGreaterThan(me.getRating()); int myRank = (int) above + 1; currentUser = new LeaderboardEntryDTO( - myRank, me.getNickname(), me.getRating(), - avatarUrl(me.getEmail()), true); + myRank, + me.getNickname(), + me.getRating(), + avatarUrl(me.getEmail()), + true + ); } - return new LeaderboardResponseDTO(top, bottom, currentUser, total); + return new LeaderboardResponseDTO( + top, + bottom, + currentUser, + total + ); } - private List mapWithRank(List users, int startRank, String currentEmail) { + private List mapWithRank(List users, int startRank, + String currentEmail) { List result = new ArrayList<>(users.size()); int rank = startRank; for (User u : users) { @@ -84,13 +111,20 @@ private List mapWithRank(List users, int startRank, S private String avatarUrl(String email) { GetObjectRequest objectRequest = GetObjectRequest.builder() - .bucket(s3Settings.bucketName()) - .key("icons/" + email) - .build(); + .bucket(s3Settings.bucketName()) + .key("icons/" + email) + .overrideConfiguration(cfg -> cfg.putHeader( + "Host", + "localhost:9000" + )) + .build(); GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() - .signatureDuration(Duration.ofMinutes(10)) - .getObjectRequest(objectRequest) - .build(); - return presigner.presignGetObject(presignRequest).url().toExternalForm(); + .signatureDuration(Duration.ofMinutes(10)) + .getObjectRequest(objectRequest) + .build(); + return presigner.presignGetObject(presignRequest).url().toExternalForm().replaceFirst( + "minio", + "localhost" + ); } } \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/MatchRoom/MatchController.java b/src/main/java/com/codzilla/backend/MatchRoom/MatchController.java index 1b0dc3c..67ba4b6 100644 --- a/src/main/java/com/codzilla/backend/MatchRoom/MatchController.java +++ b/src/main/java/com/codzilla/backend/MatchRoom/MatchController.java @@ -1,25 +1,58 @@ package com.codzilla.backend.MatchRoom; import com.codzilla.backend.MatchRoom.dto.MatchHistoryEntryDTO; +import com.codzilla.backend.PreMatch.model.Category; import com.codzilla.backend.User.User; -import lombok.RequiredArgsConstructor; +import com.codzilla.backend.judge.problem.ProblemService; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.RequiredArgsConstructor; +import java.util.UUID; import java.util.List; -@RestController +@Controller @RequestMapping("/match") -@RequiredArgsConstructor public class MatchController { private final MatchService matchService; + private final ProblemService problemService; + + public MatchController(MatchService matchService, ProblemService problemService) { + this.matchService = matchService; + this.problemService = problemService; + } - @GetMapping("/history") + @GetMapping("/{matchId}/options") + ResponseEntity getMatchOptions(@AuthenticationPrincipal User user, @PathVariable UUID matchId) { + var match = matchService.getMatchById(matchId); + if (match == null) { + return ResponseEntity.notFound().build(); + } + if (!(match.getFirstUserId().equals(user.getId()) || + match.getSecondUserId().equals(user.getId()))) { + return ResponseEntity.status(HttpStatusCode.valueOf(404)).build(); + } + try { + var statement = problemService.getStatementOfProblem(match.getProblem().getId()); + var problem = problemService.getArtefactsOfProblem(match.getProblem().getId()); + var options = match.getOptions(); + var response = new MatchOptions(problem.getName(), statement, options.get(Category.Language), options.get(Category.ProblemType), options.get(Category.ProblemLevel)); + return ResponseEntity.ok(response); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } + + @GetMapping("/history") public ResponseEntity> getMatchHistory(@AuthenticationPrincipal User user) { return ResponseEntity.ok(matchService.getMatchHistory(user.getId())); } + } + diff --git a/src/main/java/com/codzilla/backend/MatchRoom/MatchOptions.java b/src/main/java/com/codzilla/backend/MatchRoom/MatchOptions.java new file mode 100644 index 0000000..46b47f9 --- /dev/null +++ b/src/main/java/com/codzilla/backend/MatchRoom/MatchOptions.java @@ -0,0 +1,11 @@ +package com.codzilla.backend.MatchRoom; + + +public record MatchOptions( + String title, + String statement, + String language, + String problemType, + String problemLevel +) { +} diff --git a/src/main/java/com/codzilla/backend/MatchRoom/MatchService.java b/src/main/java/com/codzilla/backend/MatchRoom/MatchService.java index 27261e3..1bf7cea 100644 --- a/src/main/java/com/codzilla/backend/MatchRoom/MatchService.java +++ b/src/main/java/com/codzilla/backend/MatchRoom/MatchService.java @@ -100,7 +100,7 @@ public Problem pickProblemOfOptions(Map options) { log.info("Pick problem of options: language={}, type={}, level={}", language, problemType, problemLevel); - if (language != null && language.equalsIgnoreCase(Language.SQL.name())) { + if (language == null) { log.info("Choosing SQL problem"); Problem sqlProblem = problemService.getOrCreateRandomSqlProblem(problemLevel); log.info("Selected SQL problem: id={}, type={}", sqlProblem.getId(), sqlProblem.getType()); diff --git a/src/main/java/com/codzilla/backend/PreMatch/DraftSession/DraftSessionConfig.java b/src/main/java/com/codzilla/backend/PreMatch/DraftSession/DraftSessionConfig.java index 874b6a0..5872148 100644 --- a/src/main/java/com/codzilla/backend/PreMatch/DraftSession/DraftSessionConfig.java +++ b/src/main/java/com/codzilla/backend/PreMatch/DraftSession/DraftSessionConfig.java @@ -22,19 +22,18 @@ public ThreadPoolTaskScheduler threadPoolTaskScheduler() { public Map categorySequence() { return Map.of( Language.PY.name(), - Category.ProblemType, + Category.ProblemLevel, Language.CPP.name(), - Category.ProblemType, + Category.ProblemLevel, Language.JAVA.name(), - Category.ProblemType, - Language.SQL.name(), Category.ProblemLevel, - ProblemType.ALGORITHM.name(), - Category.ProblemLevel, + Category.Language, ProblemType.MATH.name(), - Category.ProblemLevel, + Category.Language, ProblemType.DATA_STRUCTURES.name(), + Category.Language, + ProblemType.SQL.name(), Category.ProblemLevel ); } diff --git a/src/main/java/com/codzilla/backend/PreMatch/DraftSession/DraftSessionService.java b/src/main/java/com/codzilla/backend/PreMatch/DraftSession/DraftSessionService.java index f97a538..d059bde 100644 --- a/src/main/java/com/codzilla/backend/PreMatch/DraftSession/DraftSessionService.java +++ b/src/main/java/com/codzilla/backend/PreMatch/DraftSession/DraftSessionService.java @@ -144,7 +144,7 @@ public DraftSession startDraftSession(UUID matchId, UUID firstUserId, UUID secon matchId, firstUserId, secondUserId, - Category.Language + Category.ProblemType ); draftSessionRepository.save(draftSession); startTimer(draftSession.getId()); diff --git a/src/main/java/com/codzilla/backend/PreMatch/model/Language.java b/src/main/java/com/codzilla/backend/PreMatch/model/Language.java index 258bd6d..804f62f 100644 --- a/src/main/java/com/codzilla/backend/PreMatch/model/Language.java +++ b/src/main/java/com/codzilla/backend/PreMatch/model/Language.java @@ -3,7 +3,6 @@ public enum Language implements Option { CPP(54), PY(71), - SQL(0), JAVA(63); private final int value; diff --git a/src/main/java/com/codzilla/backend/User/UserController.java b/src/main/java/com/codzilla/backend/User/UserController.java index 78fa7a7..8228215 100644 --- a/src/main/java/com/codzilla/backend/User/UserController.java +++ b/src/main/java/com/codzilla/backend/User/UserController.java @@ -113,6 +113,7 @@ private String createPresignedGetUrl(String bucketName, String keyName) { GetObjectRequest objectRequest = GetObjectRequest.builder() .bucket(bucketName) .key(keyName) + .overrideConfiguration(cfg -> cfg.putHeader("Host", "localhost:9000")) .build(); GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() @@ -130,7 +131,7 @@ private String createPresignedGetUrl(String bucketName, String keyName) { presignedRequest.httpRequest().method() ); - return presignedRequest.url().toExternalForm(); + return presignedRequest.url().toExternalForm().replaceFirst("minio", "localhost"); } diff --git a/src/main/java/com/codzilla/backend/judge/client/Artefactik0Client.java b/src/main/java/com/codzilla/backend/judge/client/Artefactik0Client.java index 4f48471..a2527b3 100644 --- a/src/main/java/com/codzilla/backend/judge/client/Artefactik0Client.java +++ b/src/main/java/com/codzilla/backend/judge/client/Artefactik0Client.java @@ -1,5 +1,7 @@ package com.codzilla.backend.judge.client; +import com.codzilla.backend.judge.problem.ProblemResponseDTO; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Data; @@ -22,68 +24,155 @@ public class Artefactik0Client { public Artefactik0Client(@Value("${artefactik0.base-url}") String baseUrl) { this.restClient = RestClient.builder() - .baseUrl(baseUrl) - .defaultHeader("Accept", "application/json") - .defaultHeader("Content-Type", "application/json") - .build(); + .baseUrl(baseUrl) + .defaultHeader( + "Accept", + "application/json" + ) + .defaultHeader( + "Content-Type", + "application/json" + ) + .build(); } public Long createProblem(CreateProblemRequest request) { try { String body = objectMapper.writeValueAsString(request); String raw = restClient.post() - .uri("/api/problems") - .contentType(MediaType.APPLICATION_JSON) - .body(body) - .retrieve() - .body(String.class); - - ProblemResponse response = objectMapper.readValue(raw, ProblemResponse.class); - log.info("Artefactik0 created problem id={}", response.getId()); + .uri("/api/problems") + .contentType(MediaType.APPLICATION_JSON) + .body(body) + .retrieve() + .body(String.class); + + ProblemResponse response = objectMapper.readValue( + raw, + ProblemResponse.class + ); + log.info( + "Artefactik0 created problem id={}", + response.getId() + ); return response.getId(); } catch (Exception e) { - throw new RuntimeException("Failed to create problem in Artefactik0", e); + throw new RuntimeException( + "Failed to create problem in Artefactik0", + e + ); + } + } + + public ProblemResponseDTO getProblem(Long problemId) { + try { + String raw = restClient.get() + .uri( + "/api/problems/" + problemId + ) + .retrieve() + .body(String.class); + + ProblemResponseDTO + response = objectMapper.readValue( + raw, + ProblemResponseDTO.class + ); + log.info( + "Artefactik0 get problem id={}", + response.getId() + ); + return response; + } catch (Exception e) { + throw new RuntimeException( + "Failed to get problem from Artefactik0", + e + ); } } + public String getStatement(Long problemId) { + try { + String raw = restClient.get() + .uri( + "/api/problems/" + problemId + "/statement" + ) + .retrieve() + .body(String.class); + + log.info( + "Artefactik0 get statement {}", + raw + ); + return raw; + } catch (Exception e) { + throw new RuntimeException( + "Failed to get statement from Artefactik0", + e + ); + } + } + + public List getTests(Long problemId) { try { List tests = restClient.get() - .uri("/api/problems/" + problemId + "/tests") - .retrieve() - .body(new ParameterizedTypeReference>() {}); + .uri("/api/problems/" + problemId + "/tests") + .retrieve() + .body(new ParameterizedTypeReference>() { + }); if (tests == null) { - throw new RuntimeException("No tests returned from Artefactik0 for problem " + problemId); + throw new RuntimeException( + "No tests returned from Artefactik0 for problem " + problemId); } - log.info("Artefactik0 returned {} tests for problem {}", tests.size(), problemId); + log.info( + "Artefactik0 returned {} tests for problem {}", + tests.size(), + problemId + ); return tests; } catch (Exception e) { - throw new RuntimeException("Failed to fetch tests from Artefactik0 for problem " + problemId, e); + throw new RuntimeException( + "Failed to fetch tests from Artefactik0 for problem " + problemId, + e + ); } } /** * Получить случайную задачу из Artefactik0 по типу и сложности. - * @param type ALGORITHM, DATA_STRUCTURES, MATH (пока заглушка -> ALGORITHM) - * @param level EASY, MEDIUM, HARD + * + * @param type ALGORITHM, DATA_STRUCTURES, MATH (пока заглушка -> ALGORITHM) + * @param level EASY, MEDIUM, HARD */ public RandomProblemResponse getRandomProblem(String type, String level) { try { String uri = UriComponentsBuilder.fromPath("/api/problems/random") - .queryParam("type", type.toUpperCase()) - .queryParam("complexity", level.toUpperCase()) - .build().toString(); + .queryParam( + "type", + type.toUpperCase() + ) + .queryParam( + "complexity", + level.toUpperCase() + ) + .build().toString(); String raw = restClient.get() - .uri(uri) - .retrieve() - .body(String.class); - return objectMapper.readValue(raw, RandomProblemResponse.class); + .uri(uri) + .retrieve() + .body(String.class); + return objectMapper.readValue( + raw, + RandomProblemResponse.class + ); } catch (Exception e) { - throw new RuntimeException("Failed to fetch random problem from Artefactik0", e); + throw new RuntimeException( + "Failed to fetch random problem from Artefactik0", + e + ); } } diff --git a/src/main/java/com/codzilla/backend/judge/problem/ProblemResponseDTO.java b/src/main/java/com/codzilla/backend/judge/problem/ProblemResponseDTO.java new file mode 100644 index 0000000..aa36be8 --- /dev/null +++ b/src/main/java/com/codzilla/backend/judge/problem/ProblemResponseDTO.java @@ -0,0 +1,24 @@ +package com.codzilla.backend.judge.problem; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class ProblemResponseDTO { + + Long id; + String name; + Integer timeLimit; + Integer memoryLimit; + boolean hasStatement; + int testCount; + String complexity; + String taskType; +} diff --git a/src/main/java/com/codzilla/backend/judge/problem/ProblemService.java b/src/main/java/com/codzilla/backend/judge/problem/ProblemService.java index b2cd7f2..16a4c5a 100644 --- a/src/main/java/com/codzilla/backend/judge/problem/ProblemService.java +++ b/src/main/java/com/codzilla/backend/judge/problem/ProblemService.java @@ -13,7 +13,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.springframework.web.client.HttpClientErrorException; import java.util.*; @@ -205,4 +204,12 @@ public Problem getOrCreateRandomSqlProblem(String level) { } throw new RuntimeException("No SQL tasks found at any level (EASY, MEDIUM, HARD)"); } + + public ProblemResponseDTO getArtefactsOfProblem(Long problemId) { + return artefactik0Client.getProblem(problemId); + } + + public String getStatementOfProblem(Long problemId) { + return artefactik0Client.getStatement(problemId); + } }