From 34bbd1c20f0e319791e8e2d6429aa6b58871f937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 10 Mar 2026 18:46:45 +0300 Subject: [PATCH 01/16] edit : Dockerfile --- Dockerfile | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6bff3d3..f5f1458 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,11 @@ -FROM eclipse-temurin:21-jre-alpine -WORKDIR /app -# Копируем готовый jar из папки build/libs -COPY build/libs/*.jar app.jar -ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file +# Этап 1 — сборка jar внутри Docker + FROM gradle:8-jdk21-alpine AS builder + WORKDIR /app + COPY . . + RUN gradle build -x test + + # Этап 2 — запуск + FROM eclipse-temurin:21-jre-alpine + WORKDIR /app + COPY --from=builder /app/build/libs/*.jar app.jar + ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file From 3278394e6bbc024d526951c24d7609bd79996fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 10 Mar 2026 23:32:39 +0300 Subject: [PATCH 02/16] add : logic --- build.gradle | 3 + .../Sandbox/judge0/Judge0Client.java | 51 +++++ .../Sandbox/judge0/Judge0Result.java | 4 + .../Sandbox/polygon/CreateProblemRequest.java | 20 ++ .../Sandbox/polygon/PolygonClient.java | 186 ++++++++++++++++++ .../Sandbox/polygon/PolygonProblem.java | 20 ++ .../Sandbox/polygon/PolygonResponse.java | 18 ++ .../controller/Sandbox/problem/Problem.java | 30 +++ .../Sandbox/problem/ProblemController.java | 29 +++ .../Sandbox/problem/ProblemRepository.java | 13 ++ .../Sandbox/problem/ProblemService.java | 60 ++++++ .../application-dev.properties.example | 4 - .../resources/application-prod.properties | 19 +- src/main/resources/application.properties | 6 +- 14 files changed, 457 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java create mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Result.java create mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/polygon/CreateProblemRequest.java create mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonClient.java create mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonProblem.java create mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonResponse.java create mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/problem/Problem.java create mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemController.java create mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemRepository.java create mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java delete mode 100644 src/main/resources/application-dev.properties.example diff --git a/build.gradle b/build.gradle index 8eaebe0..0c16faa 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,9 @@ dependencies { developmentOnly 'org.springframework.boot:spring-boot-devtools' + compileOnly 'org.projectlombok:lombok' // добавить + annotationProcessor 'org.projectlombok:lombok' + runtimeOnly 'org.postgresql:postgresql' testRuntimeOnly 'com.h2database:h2' diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java new file mode 100644 index 0000000..002d703 --- /dev/null +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java @@ -0,0 +1,51 @@ +package com.codzilla.backend.controller.Sandbox.judge0; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; + +@Slf4j +@Component +public class Judge0Client { + + private final RestClient restClient; + + public Judge0Client(@Value("${judge0.base-url}") String baseUrl) { + this.restClient = RestClient.builder() + .baseUrl(baseUrl) + .build(); + } + + // отправить решение на один тест, вернуть токен + public String submit(String sourceCode, int languageId, String stdin) { + var request = new SubmissionRequest(sourceCode, languageId, stdin); + + SubmissionResponse response = restClient.post() + .uri("/submissions?wait=true") // wait=true — ждём результат сразу + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .body(request) + .retrieve() + .body(SubmissionResponse.class); + + log.info("Judge0 response: status={}, stdout={}, stderr={}", + response.status(), response.stdout(), response.stderr()); + + return response.status().description(); + } + + record SubmissionRequest( + String source_code, + int language_id, + String stdin + ) {} + + record SubmissionResponse( + String stdout, + String stderr, + String compile_output, + Status status + ) { + record Status(int id, String description) {} + } +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Result.java b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Result.java new file mode 100644 index 0000000..9032eb4 --- /dev/null +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Result.java @@ -0,0 +1,4 @@ +package com.codzilla.backend.controller.Sandbox.judge0; + +public class Judge0Result { +} diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/CreateProblemRequest.java b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/CreateProblemRequest.java new file mode 100644 index 0000000..9dc8bf8 --- /dev/null +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/CreateProblemRequest.java @@ -0,0 +1,20 @@ +package com.codzilla.backend.controller.Sandbox.polygon; + +import com.codzilla.backend.controller.Sandbox.problem.Problem; +import lombok.Data; +import java.util.List; + +// То что принимаем от пользователя +@Data +public class CreateProblemRequest { + private String name; // название задачи + private Problem.ProblemType type; + private Problem.ProblemLevel level; + private List tests; // список тестов + + @Data + public static class TestCase { + private String input; // "1 2" + private String output; // "3" + } +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonClient.java b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonClient.java new file mode 100644 index 0000000..62e4ba7 --- /dev/null +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonClient.java @@ -0,0 +1,186 @@ +package com.codzilla.backend.controller.Sandbox.polygon; + +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; +import com.fasterxml.jackson.databind.ObjectMapper; + + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.time.Instant; + +import java.util.Random; +import java.util.TreeMap; +import java.util.stream.Collectors; + + + + +@Slf4j +@Component +public class PolygonClient { + + private static final String BASE_URL = "https://polygon.codeforces.com/api/"; + + @Value("${polygon.api.key}") + private String apiKey; + + @Value("${polygon.api.secret}") + private String apiSecret; + + private final RestClient restClient; + + public PolygonClient() { + this.restClient = RestClient.builder() + .baseUrl(BASE_URL) + .defaultHeader("Accept", "application/json") + .defaultHeader("Content-Type", "application/json") + .build(); + } + + + public PolygonProblem getProblemTests(String problemId) { + var params = new TreeMap(); + params.put("problemId", problemId); + params.put("testset", "tests"); + + String url = buildSignedUrl("problem.tests", params); + + String raw = restClient.get() + .uri(url) + .retrieve() + .onStatus(status -> true, (req, res) -> {}) + .body(String.class); + + log.info("getProblemTests response: {}", raw); + + try { + PolygonProblem result = objectMapper.readValue(raw, PolygonProblem.class); + if (!"OK".equals(result.getStatus())) { + throw new RuntimeException("Polygon error getting tests: " + raw); + } + return result; + } catch (Exception e) { + throw new RuntimeException("Failed to parse tests response: " + raw, e); + } + } + + + // Подписывает запрос по алгоритму Polygon + private String buildSignedUrl(String method, TreeMap params) { + // rand как hex, как в рабочем коде + String rand = String.format("%06x", new SecureRandom().nextInt(0xFFFFFF)); + + params.put("apiKey", apiKey); + params.put("time", String.valueOf(Instant.now().getEpochSecond())); + + // для подписи — БЕЗ url-encoding, просто key=value + String paramStr = params.entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue()) + .collect(Collectors.joining("&")); + + String dataToSign = rand + "/" + method + "?" + paramStr + "#" + apiSecret; + log.info("toSign: {}", dataToSign); + + String hash = sha512(dataToSign); + params.put("apiSig", rand + hash); + + // для URL — С url-encoding + String query = params.entrySet().stream() + .map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8) + + "=" + + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8)) + .collect(Collectors.joining("&")); + + return method + "?" + query; + } + + + // Заменить hmacSha512 на обычный SHA512 + private String sha512(String data) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-512"); + byte[] bytes = md.digest(data.getBytes("UTF-8")); // явно UTF-8 + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); // %02x гарантирует ведущий ноль + } + return sb.toString(); + } catch (Exception e) { + throw new RuntimeException("Failed to sign request", e); + } + } + + + + private String generateRand() { + String chars = "abcdefghijklmnopqrstuvwxyz0123456789"; + StringBuilder sb = new StringBuilder(); + Random random = new Random(); + for (int i = 0; i < 6; i++) { + sb.append(chars.charAt(random.nextInt(chars.length()))); + } + return sb.toString(); + } + + + + + private final ObjectMapper objectMapper = new ObjectMapper(); + + public String createProblem(String name) { + var params = new TreeMap(); + params.put("name", name); + + String url = buildSignedUrl("problem.create", params); + + // всегда получаем как строку + String raw = restClient.post() + .uri(url) + .retrieve() + .onStatus(status -> true, (req, res) -> {}) // не бросать исключение на 4xx + .body(String.class); + + log.info("Polygon response: {}", raw); + + try { + PolygonResponse response = objectMapper.readValue(raw, PolygonResponse.class); + if (!"OK".equals(response.getStatus())) { + throw new RuntimeException("Polygon error: " + response.getComment()); + } + return response.getResult().getId().toString(); + } catch (Exception e) { + throw new RuntimeException("Failed to parse Polygon response: " + raw, e); + } + } + + + + public void saveTest(String problemId, int index, String input, String output) { + var params = new TreeMap(); + params.put("problemId", problemId); + params.put("testset", "tests"); // добавить это + params.put("testIndex", String.valueOf(index)); + params.put("testInput", input); + params.put("testOutput", output); + params.put("testUseInStatements", "false"); + + String url = buildSignedUrl("problem.saveTest", params); + + String raw = restClient.post() + .uri(url) + .retrieve() + .onStatus(status -> true, (req, res) -> {}) + .body(String.class); + + log.info("saveTest response: {}", raw); + + } + + +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonProblem.java b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonProblem.java new file mode 100644 index 0000000..a9ec726 --- /dev/null +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonProblem.java @@ -0,0 +1,20 @@ +package com.codzilla.backend.controller.Sandbox.polygon; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import java.util.List; + +@Data + +public class PolygonProblem { + private String status; // "OK" или "FAILED" + private List result; + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Test { + private int index; + private String input; + private String output; // expected output + } +} diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonResponse.java b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonResponse.java new file mode 100644 index 0000000..6e50f74 --- /dev/null +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonResponse.java @@ -0,0 +1,18 @@ +package com.codzilla.backend.controller.Sandbox.polygon; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +public class PolygonResponse { + private String status; + private String comment; + private ProblemResult result; + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) // игнорировать неизвестные поля + public static class ProblemResult { + private Long id; + private String owner; + private String name; + } +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/Problem.java b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/Problem.java new file mode 100644 index 0000000..872aecf --- /dev/null +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/Problem.java @@ -0,0 +1,30 @@ +package com.codzilla.backend.controller.Sandbox.problem; + +import jakarta.persistence.*; +import lombok.Data; + +@Entity +@Table(name = "problems") +@Data +public class Problem { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String polygonToken; // индекс/токен из Polygon + + @Enumerated(EnumType.STRING) + private ProblemType type; // ALGORITHM, DATA_STRUCTURES и т.д. + + @Enumerated(EnumType.STRING) + private ProblemLevel level; // EASY, MEDIUM, HARD + + public enum ProblemType { + ALGORITHM, DATA_STRUCTURES, MATH + } + + public enum ProblemLevel { + EASY, MEDIUM, HARD + } +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemController.java b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemController.java new file mode 100644 index 0000000..87b915b --- /dev/null +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemController.java @@ -0,0 +1,29 @@ +package com.codzilla.backend.controller.Sandbox.problem; + +import com.codzilla.backend.controller.Sandbox.polygon.CreateProblemRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/problems") +@RequiredArgsConstructor +public class ProblemController { + + private final ProblemService problemService; + + @PostMapping("/create") + public ResponseEntity createProblem(@RequestBody CreateProblemRequest request) { + Problem saved = problemService.createProblem(request); + return ResponseEntity.ok(saved); + } + + @PostMapping("/{id}/submit") + public ResponseEntity submit( + @PathVariable Long id, + @RequestParam int languageId, + @RequestBody String sourceCode) { + String result = problemService.submitSolution(id, sourceCode, languageId); + return ResponseEntity.ok(result); + } +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemRepository.java b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemRepository.java new file mode 100644 index 0000000..5cd94be --- /dev/null +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemRepository.java @@ -0,0 +1,13 @@ +package com.codzilla.backend.controller.Sandbox.problem; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProblemRepository extends JpaRepository { + // JpaRepository уже даёт тебе: + // save(problem) — сохранить + // findById(id) — найти по id + // findAll() — все задачи + // deleteById(id) — удалить +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java new file mode 100644 index 0000000..57dfe2c --- /dev/null +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java @@ -0,0 +1,60 @@ +package com.codzilla.backend.controller.Sandbox.problem; + +import com.codzilla.backend.controller.Sandbox.judge0.Judge0Client; +import com.codzilla.backend.controller.Sandbox.polygon.CreateProblemRequest; +import com.codzilla.backend.controller.Sandbox.polygon.PolygonClient; +import com.codzilla.backend.controller.Sandbox.polygon.PolygonProblem; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ProblemService { + + private final PolygonClient polygonClient; + private final ProblemRepository problemRepository; + private final Judge0Client judge0Client; + + + public Problem createProblem(CreateProblemRequest request) { + String polygonId = polygonClient.createProblem(request.getName()); + + var tests = request.getTests(); + for (int i = 0; i < tests.size(); i++) { + var test = tests.get(i); + polygonClient.saveTest(polygonId, i + 1, test.getInput(), test.getOutput()); + } + + Problem problem = new Problem(); + problem.setPolygonToken(polygonId); + problem.setType(request.getType()); + problem.setLevel(request.getLevel()); + + return problemRepository.save(problem); + } + + public String submitSolution(Long problemId, String sourceCode, int languageId) { + // 1. достать задачу из БД + Problem problem = problemRepository.findById(problemId) + .orElseThrow(() -> new RuntimeException("Problem not found: " + problemId)); + + // 2. получить тесты из Polygon + PolygonProblem polygonProblem = polygonClient.getProblemTests(problem.getPolygonToken()); + + // 3. прогнать по каждому тесту + List results = new ArrayList<>(); + for (var test : polygonProblem.getResult()) { + String verdict = judge0Client.submit(sourceCode, languageId, test.getInput()); + results.add("Test " + test.getIndex() + ": " + verdict); + if (!"Accepted".equals(verdict)) break; // стоп на первой ошибке + } + + return String.join("\n", results); + } + +} \ No newline at end of file diff --git a/src/main/resources/application-dev.properties.example b/src/main/resources/application-dev.properties.example deleted file mode 100644 index 959e765..0000000 --- a/src/main/resources/application-dev.properties.example +++ /dev/null @@ -1,4 +0,0 @@ -spring.datasource.url=jdbc:postgresql://localhost:5433/testingdb -spring.datasource.username=myuser -spring.datasource.password=secret -spring.jpa.hibernate.ddl-auto=update \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 9ec9103..55ef647 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -1,4 +1,21 @@ spring.datasource.url=${DB_URL} spring.datasource.username=${DB_USERNAME} spring.datasource.password=${DB_PASSWORD} -spring.jpa.hibernate.ddl-auto=update \ No newline at end of file + +polygon.api.key=${POLYGON_KEY} +polygon.api.secret=${POLYGON_SECRET} + + +# ????? ? docker-compose.yml ????? ????? ?? ?????????? ??? ??? +#backend: +#build: ./backend +#ports: +#- "8080:8080" +#environment: +#SPRING_PROFILES_ACTIVE: prod +#DB_URL: jdbc:postgresql://postgres:5432/codzilla +#DB_USERNAME: user +#DB_PASSWORD: password +#depends_on: +#- postgres +#- judge0-server \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7060d81..4347897 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,6 @@ spring.application.name=Backend -spring.jpa.show-sql=true \ No newline at end of file +spring.jpa.show-sql=true + +# ??????? +spring.jpa.hibernate.ddl-auto=update + From 307f363db0a95f4a4e8767c9b7f14014fcff256d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 11 Mar 2026 00:17:56 +0300 Subject: [PATCH 03/16] add : logic --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 0c16faa..7cedf64 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,10 @@ group = 'com.codzilla' version = '0.0.1' description = 'Backend' +jar { + enabled = false +} + java { toolchain { languageVersion = JavaLanguageVersion.of(21) From 9fc6218ccd00cb96fc40e7d16402f1e13de8a5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 11 Mar 2026 00:18:25 +0300 Subject: [PATCH 04/16] edit build.gradle --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 7cedf64..b4b4cc0 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ version = '0.0.1' description = 'Backend' jar { + enabled = false } From 616dd4354a76783caad32e8038c698af3118e016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 11 Mar 2026 00:49:23 +0300 Subject: [PATCH 05/16] edit : Judge0Client.java --- .../Sandbox/judge0/Judge0Client.java | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java index 002d703..74036cd 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java @@ -4,6 +4,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClient; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; @Slf4j @Component @@ -29,23 +32,43 @@ public String submit(String sourceCode, int languageId, String stdin) { .body(SubmissionResponse.class); log.info("Judge0 response: status={}, stdout={}, stderr={}", - response.status(), response.stdout(), response.stderr()); + response.getStatus(), response.getStdout(), response.getStderr()); - return response.status().description(); + return response.getStatus().getDescription(); } - record SubmissionRequest( - String source_code, - int language_id, - String stdin - ) {} - - record SubmissionResponse( - String stdout, - String stderr, - String compile_output, - Status status - ) { - record Status(int id, String description) {} + + + @Data + public static class SubmissionRequest { + @JsonProperty("source_code") + private String sourceCode; + + @JsonProperty("language_id") + private int languageId; + + private String stdin; + + public SubmissionRequest(String sourceCode, int languageId, String stdin) { + this.sourceCode = sourceCode; + this.languageId = languageId; + this.stdin = stdin; + } + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class SubmissionResponse { + private String stdout; + private String stderr; + private String compile_output; + private Status status; + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Status { + private int id; + private String description; + } } } \ No newline at end of file From 927c45ddb13a3695317b4feae779160c9c0a4702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 11 Mar 2026 01:10:31 +0300 Subject: [PATCH 06/16] edit : Judge0Client.java --- .../Sandbox/judge0/Judge0Client.java | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java index 74036cd..b1f7c89 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java @@ -1,12 +1,11 @@ package com.codzilla.backend.controller.Sandbox.judge0; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClient; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; @Slf4j @Component @@ -20,12 +19,14 @@ public Judge0Client(@Value("${judge0.base-url}") String baseUrl) { .build(); } - // отправить решение на один тест, вернуть токен public String submit(String sourceCode, int languageId, String stdin) { var request = new SubmissionRequest(sourceCode, languageId, stdin); + log.info("Sending to Judge0: source_code={}, language_id={}, stdin={}", + request.source_code, request.language_id, request.stdin); + SubmissionResponse response = restClient.post() - .uri("/submissions?wait=true") // wait=true — ждём результат сразу + .uri("/submissions?wait=true") .contentType(org.springframework.http.MediaType.APPLICATION_JSON) .body(request) .retrieve() @@ -37,21 +38,15 @@ public String submit(String sourceCode, int languageId, String stdin) { return response.getStatus().getDescription(); } - - - @Data + // БЕЗ @Data и БЕЗ private — публичные поля, Jackson читает напрямую public static class SubmissionRequest { - @JsonProperty("source_code") - private String sourceCode; - - @JsonProperty("language_id") - private int languageId; - - private String stdin; + public String source_code; + public int language_id; + public String stdin; public SubmissionRequest(String sourceCode, int languageId, String stdin) { - this.sourceCode = sourceCode; - this.languageId = languageId; + this.source_code = sourceCode; + this.language_id = languageId; this.stdin = stdin; } } From df7fd78e4e4d19002070542900c59ac68288f1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 11 Mar 2026 01:20:47 +0300 Subject: [PATCH 07/16] edit : Judge0Client --- .../codzilla/backend/controller/Sandbox/judge0/Judge0Client.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java index b1f7c89..8116aaf 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java @@ -32,6 +32,7 @@ public String submit(String sourceCode, int languageId, String stdin) { .retrieve() .body(SubmissionResponse.class); + assert response != null; log.info("Judge0 response: status={}, stdout={}, stderr={}", response.getStatus(), response.getStdout(), response.getStderr()); From 6fb96e068533966969642fe3d012a06778963224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 11 Mar 2026 01:30:30 +0300 Subject: [PATCH 08/16] edit : Judge0Client --- .../backend/controller/Sandbox/judge0/Judge0Client.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java index 8116aaf..fd0f7b7 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java @@ -19,7 +19,7 @@ public Judge0Client(@Value("${judge0.base-url}") String baseUrl) { .build(); } - public String submit(String sourceCode, int languageId, String stdin) { + public String submit(String sourceCode, Integer languageId, String stdin) { var request = new SubmissionRequest(sourceCode, languageId, stdin); log.info("Sending to Judge0: source_code={}, language_id={}, stdin={}", @@ -42,10 +42,10 @@ public String submit(String sourceCode, int languageId, String stdin) { // БЕЗ @Data и БЕЗ private — публичные поля, Jackson читает напрямую public static class SubmissionRequest { public String source_code; - public int language_id; + public Integer language_id; public String stdin; - public SubmissionRequest(String sourceCode, int languageId, String stdin) { + public SubmissionRequest(String sourceCode, Integer languageId, String stdin) { this.source_code = sourceCode; this.language_id = languageId; this.stdin = stdin; @@ -63,7 +63,7 @@ public static class SubmissionResponse { @Data @JsonIgnoreProperties(ignoreUnknown = true) public static class Status { - private int id; + private Integer id; private String description; } } From 8083a1f2c40047cc55f79d1182bf125e3d941751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 11 Mar 2026 01:33:32 +0300 Subject: [PATCH 09/16] edit : Judge0Client --- .../backend/controller/Sandbox/judge0/Judge0Client.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java index fd0f7b7..43eaffe 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java @@ -19,7 +19,7 @@ public Judge0Client(@Value("${judge0.base-url}") String baseUrl) { .build(); } - public String submit(String sourceCode, Integer languageId, String stdin) { + public String submit(String sourceCode, int languageId, String stdin) { var request = new SubmissionRequest(sourceCode, languageId, stdin); log.info("Sending to Judge0: source_code={}, language_id={}, stdin={}", @@ -45,7 +45,7 @@ public static class SubmissionRequest { public Integer language_id; public String stdin; - public SubmissionRequest(String sourceCode, Integer languageId, String stdin) { + public SubmissionRequest(String sourceCode, int languageId, String stdin) { this.source_code = sourceCode; this.language_id = languageId; this.stdin = stdin; @@ -63,7 +63,7 @@ public static class SubmissionResponse { @Data @JsonIgnoreProperties(ignoreUnknown = true) public static class Status { - private Integer id; + private int id; private String description; } } From 21d27e03512b949e3b89b48acedf91faf0413067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 11 Mar 2026 10:46:52 +0300 Subject: [PATCH 10/16] edit : Judge0Client --- .../Sandbox/judge0/Judge0Client.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java index 43eaffe..9b6944f 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java @@ -1,17 +1,20 @@ package com.codzilla.backend.controller.Sandbox.judge0; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClient; +import org.springframework.http.MediaType; @Slf4j @Component public class Judge0Client { private final RestClient restClient; + private final ObjectMapper objectMapper = new ObjectMapper(); public Judge0Client(@Value("${judge0.base-url}") String baseUrl) { this.restClient = RestClient.builder() @@ -20,26 +23,29 @@ public Judge0Client(@Value("${judge0.base-url}") String baseUrl) { } public String submit(String sourceCode, int languageId, String stdin) { - var request = new SubmissionRequest(sourceCode, languageId, stdin); + try { + // сериализуем вручную + String body = objectMapper.writeValueAsString(new SubmissionRequest(sourceCode, languageId, stdin)); + log.info("Sending to Judge0 body: {}", body); - log.info("Sending to Judge0: source_code={}, language_id={}, stdin={}", - request.source_code, request.language_id, request.stdin); + String raw = restClient.post() + .uri("/submissions?wait=true") + .contentType(MediaType.APPLICATION_JSON) + .body(body) + .retrieve() + .onStatus(status -> true, (req, res) -> {}) + .body(String.class); - SubmissionResponse response = restClient.post() - .uri("/submissions?wait=true") - .contentType(org.springframework.http.MediaType.APPLICATION_JSON) - .body(request) - .retrieve() - .body(SubmissionResponse.class); + log.info("Judge0 response: {}", raw); - assert response != null; - log.info("Judge0 response: status={}, stdout={}, stderr={}", - response.getStatus(), response.getStdout(), response.getStderr()); + SubmissionResponse response = objectMapper.readValue(raw, SubmissionResponse.class); + return response.getStatus().getDescription(); - return response.getStatus().getDescription(); + } catch (Exception e) { + throw new RuntimeException("Judge0 error: " + e.getMessage(), e); + } } - // БЕЗ @Data и БЕЗ private — публичные поля, Jackson читает напрямую public static class SubmissionRequest { public String source_code; public Integer language_id; From 7c0553390cf22db7da6a80d9a681ccaf0d7ccc71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Thu, 12 Mar 2026 11:18:42 +0300 Subject: [PATCH 11/16] delete comments and unused libs and methods --- .../codzilla/backend/controller/Coffee.java | 50 ----------- .../backend/controller/DataLoader.java | 26 ------ .../controller/RestApiDemoController.java | 85 ------------------- .../Sandbox/judge0/Judge0Client.java | 1 - .../Sandbox/judge0/Judge0Result.java | 4 - .../Sandbox/polygon/CreateProblemRequest.java | 1 - .../Sandbox/polygon/PolygonClient.java | 23 +---- .../Sandbox/polygon/PolygonProblem.java | 4 +- .../Sandbox/polygon/PolygonResponse.java | 2 +- .../controller/Sandbox/problem/Problem.java | 6 +- .../Sandbox/problem/ProblemRepository.java | 17 ++-- .../Sandbox/problem/ProblemService.java | 6 +- 12 files changed, 23 insertions(+), 202 deletions(-) delete mode 100644 src/main/java/com/codzilla/backend/controller/Coffee.java delete mode 100644 src/main/java/com/codzilla/backend/controller/DataLoader.java delete mode 100644 src/main/java/com/codzilla/backend/controller/RestApiDemoController.java delete mode 100644 src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Result.java diff --git a/src/main/java/com/codzilla/backend/controller/Coffee.java b/src/main/java/com/codzilla/backend/controller/Coffee.java deleted file mode 100644 index 01c16ba..0000000 --- a/src/main/java/com/codzilla/backend/controller/Coffee.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.codzilla.backend.controller; - - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; - -import java.util.UUID; - -@Entity -public class Coffee { - - - @Id - private String id ; - private String name; - public void setId(String id) { - this.id = id; - } - - - @JsonCreator - public Coffee(@JsonProperty("id") String id, @JsonProperty("name") String name) { - this.id = id; - this.name = name; - } - - public Coffee() { - this.id = UUID.randomUUID().toString(); - this.name = "DEFAULT"; - - } - - public Coffee(String name) { - this(UUID.randomUUID().toString() , name) ; - } - - public String getId() { - return id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} diff --git a/src/main/java/com/codzilla/backend/controller/DataLoader.java b/src/main/java/com/codzilla/backend/controller/DataLoader.java deleted file mode 100644 index dad4e90..0000000 --- a/src/main/java/com/codzilla/backend/controller/DataLoader.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.codzilla.backend.controller; - -import com.codzilla.backend.repository.CoffeeRepository; -import jakarta.annotation.PostConstruct; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -public class DataLoader { - public final CoffeeRepository coffeeRepository; - - public DataLoader(CoffeeRepository coffeeRepository) { - this.coffeeRepository = coffeeRepository; - } - - @PostConstruct - private void loadData() { - coffeeRepository.saveAll(List.of( - new Coffee("Café Cereza"), - new Coffee("Café Ganador"), - new Coffee("Café Lareño"), - new Coffee("Café Três Pontas") - )); - } -} diff --git a/src/main/java/com/codzilla/backend/controller/RestApiDemoController.java b/src/main/java/com/codzilla/backend/controller/RestApiDemoController.java deleted file mode 100644 index e7c43a8..0000000 --- a/src/main/java/com/codzilla/backend/controller/RestApiDemoController.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.codzilla.backend.controller; - -import com.codzilla.backend.repository.CoffeeRepository; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.Optional; - -@RestController -@RequestMapping("/coffee") -public class RestApiDemoController { - - private final CoffeeRepository coffeeRepository; -// private final List coffees = new ArrayList<>(); - - public RestApiDemoController(CoffeeRepository coffeeRepository) { - this.coffeeRepository = coffeeRepository; - - this.coffeeRepository.saveAll(List.of( - new Coffee("Café Cereza"), - new Coffee("Café Ganador"), - new Coffee("Café Lareño"), - new Coffee("Café Três Pontas") - )); - } - - - - @RequestMapping(value = "", method = RequestMethod.GET) - Iterable getCoffees() { - return coffeeRepository.findAll(); - } - - @GetMapping("/{id}") - Optional getCoffee(@PathVariable String id) { - return coffeeRepository.findById(id); - } - - @PostMapping - Coffee postCoffee(@RequestBody Coffee coffee) { - return coffeeRepository.save(coffee); - } - -// @PutMapping("/{id}") -// Coffee putCoffee(@PathVariable String id, @RequestBody Coffee coffee) { -// int coffeeIndex = -1; -// for (Coffee c: coffees) { -// if (c.getId().equals(id)) { -// coffeeIndex = coffees.indexOf(c); -// coffees.set(coffeeIndex, coffee); -// } -// } -// return (coffeeIndex == -1) ? postCoffee(coffee) : coffee; -// } - -// @PutMapping("/{id}") -// ResponseEntity putCoffee(@PathVariable String id, -// @RequestBody Coffee coffee) { -// int coffeeIndex = -1; -// for (Coffee c: coffees) { -// if (c.getId().equals(id)) { -// coffeeIndex = coffees.indexOf(c); -// coffees.set(coffeeIndex, coffee); -// } -// } -// return (coffeeIndex == -1) ? -// new ResponseEntity<>(postCoffee(coffee), HttpStatus.CREATED) : -// new ResponseEntity<>(coffee, HttpStatus.OK); -// } - - @PutMapping("/{id}") - ResponseEntity putCoffee(@PathVariable String id , @RequestBody Coffee coffee){ - return (!coffeeRepository.existsById(id)) ? - new ResponseEntity<>(coffeeRepository.save(coffee) , HttpStatus.CREATED) : - new ResponseEntity<>(coffeeRepository.save(coffee) , HttpStatus.OK); - - } - - @DeleteMapping("/{id}") - void deleteCoffee(@PathVariable String id) { - coffeeRepository.deleteById(id); - } -} diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java index 9b6944f..65ae69e 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Client.java @@ -24,7 +24,6 @@ public Judge0Client(@Value("${judge0.base-url}") String baseUrl) { public String submit(String sourceCode, int languageId, String stdin) { try { - // сериализуем вручную String body = objectMapper.writeValueAsString(new SubmissionRequest(sourceCode, languageId, stdin)); log.info("Sending to Judge0 body: {}", body); diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Result.java b/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Result.java deleted file mode 100644 index 9032eb4..0000000 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/judge0/Judge0Result.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.codzilla.backend.controller.Sandbox.judge0; - -public class Judge0Result { -} diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/CreateProblemRequest.java b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/CreateProblemRequest.java index 9dc8bf8..2107354 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/CreateProblemRequest.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/CreateProblemRequest.java @@ -4,7 +4,6 @@ import lombok.Data; import java.util.List; -// То что принимаем от пользователя @Data public class CreateProblemRequest { private String name; // название задачи diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonClient.java b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonClient.java index 62e4ba7..90b4764 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonClient.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonClient.java @@ -1,6 +1,5 @@ package com.codzilla.backend.controller.Sandbox.polygon; -import com.fasterxml.jackson.core.JsonProcessingException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -71,15 +70,12 @@ public PolygonProblem getProblemTests(String problemId) { } - // Подписывает запрос по алгоритму Polygon private String buildSignedUrl(String method, TreeMap params) { - // rand как hex, как в рабочем коде String rand = String.format("%06x", new SecureRandom().nextInt(0xFFFFFF)); params.put("apiKey", apiKey); params.put("time", String.valueOf(Instant.now().getEpochSecond())); - // для подписи — БЕЗ url-encoding, просто key=value String paramStr = params.entrySet().stream() .map(e -> e.getKey() + "=" + e.getValue()) .collect(Collectors.joining("&")); @@ -90,7 +86,7 @@ private String buildSignedUrl(String method, TreeMap params) { String hash = sha512(dataToSign); params.put("apiSig", rand + hash); - // для URL — С url-encoding + String query = params.entrySet().stream() .map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8) + "=" + @@ -101,7 +97,7 @@ private String buildSignedUrl(String method, TreeMap params) { } - // Заменить hmacSha512 на обычный SHA512 + private String sha512(String data) { try { MessageDigest md = MessageDigest.getInstance("SHA-512"); @@ -118,17 +114,6 @@ private String sha512(String data) { - private String generateRand() { - String chars = "abcdefghijklmnopqrstuvwxyz0123456789"; - StringBuilder sb = new StringBuilder(); - Random random = new Random(); - for (int i = 0; i < 6; i++) { - sb.append(chars.charAt(random.nextInt(chars.length()))); - } - return sb.toString(); - } - - private final ObjectMapper objectMapper = new ObjectMapper(); @@ -139,7 +124,7 @@ public String createProblem(String name) { String url = buildSignedUrl("problem.create", params); - // всегда получаем как строку + String raw = restClient.post() .uri(url) .retrieve() @@ -164,7 +149,7 @@ public String createProblem(String name) { public void saveTest(String problemId, int index, String input, String output) { var params = new TreeMap(); params.put("problemId", problemId); - params.put("testset", "tests"); // добавить это + params.put("testset", "tests"); params.put("testIndex", String.valueOf(index)); params.put("testInput", input); params.put("testOutput", output); diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonProblem.java b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonProblem.java index a9ec726..1cc0f20 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonProblem.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonProblem.java @@ -7,7 +7,7 @@ @Data public class PolygonProblem { - private String status; // "OK" или "FAILED" + private String status; private List result; @Data @@ -15,6 +15,6 @@ public class PolygonProblem { public static class Test { private int index; private String input; - private String output; // expected output + private String output; } } diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonResponse.java b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonResponse.java index 6e50f74..b7a56eb 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonResponse.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/polygon/PolygonResponse.java @@ -9,7 +9,7 @@ public class PolygonResponse { private ProblemResult result; @Data - @JsonIgnoreProperties(ignoreUnknown = true) // игнорировать неизвестные поля + @JsonIgnoreProperties(ignoreUnknown = true) public static class ProblemResult { private Long id; private String owner; diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/Problem.java b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/Problem.java index 872aecf..aca79a0 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/Problem.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/Problem.java @@ -12,13 +12,13 @@ public class Problem { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String polygonToken; // индекс/токен из Polygon + private String polygonToken; @Enumerated(EnumType.STRING) - private ProblemType type; // ALGORITHM, DATA_STRUCTURES и т.д. + private ProblemType type; @Enumerated(EnumType.STRING) - private ProblemLevel level; // EASY, MEDIUM, HARD + private ProblemLevel level; public enum ProblemType { ALGORITHM, DATA_STRUCTURES, MATH diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemRepository.java b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemRepository.java index 5cd94be..9f4127d 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemRepository.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemRepository.java @@ -5,9 +5,14 @@ @Repository public interface ProblemRepository extends JpaRepository { - // JpaRepository уже даёт тебе: - // save(problem) — сохранить - // findById(id) — найти по id - // findAll() — все задачи - // deleteById(id) — удалить -} \ No newline at end of file +} + +/* + JpaRepository уже даёт : + save(problem) — сохранить + findById(id) — найти по id + findAll() — все задачи + deleteById(id) — удалить +*/ + + diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java index 57dfe2c..3085b38 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java @@ -39,19 +39,17 @@ public Problem createProblem(CreateProblemRequest request) { } public String submitSolution(Long problemId, String sourceCode, int languageId) { - // 1. достать задачу из БД Problem problem = problemRepository.findById(problemId) .orElseThrow(() -> new RuntimeException("Problem not found: " + problemId)); - // 2. получить тесты из Polygon PolygonProblem polygonProblem = polygonClient.getProblemTests(problem.getPolygonToken()); - // 3. прогнать по каждому тесту + List results = new ArrayList<>(); for (var test : polygonProblem.getResult()) { String verdict = judge0Client.submit(sourceCode, languageId, test.getInput()); results.add("Test " + test.getIndex() + ": " + verdict); - if (!"Accepted".equals(verdict)) break; // стоп на первой ошибке + if (!"Accepted".equals(verdict)) break; } return String.join("\n", results); From 29358aad6afee2872f2cc8946efd1e3053376395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 18 Mar 2026 11:29:28 +0300 Subject: [PATCH 12/16] add : kafka logic --- build.gradle | 4 +- .../Sandbox/problem/ProblemController.java | 19 ++++- .../backend/kafka/SubmissionConsumer.java | 33 +++++++++ .../backend/kafka/SubmissionMessage.java | 15 ++++ .../backend/kafka/SubmissionProducer.java | 19 +++++ .../backend/repository/CoffeeRepository.java | 7 -- .../websocket/ResultWebSocketHandler.java | 56 +++++++++++++++ .../backend/websocket/WebSocketConfig.java | 21 ++++++ .../resources/application-prod.properties | 10 +++ .../controller/CoffeeControllerTest.java | 70 ------------------- 10 files changed, 174 insertions(+), 80 deletions(-) create mode 100644 src/main/java/com/codzilla/backend/kafka/SubmissionConsumer.java create mode 100644 src/main/java/com/codzilla/backend/kafka/SubmissionMessage.java create mode 100644 src/main/java/com/codzilla/backend/kafka/SubmissionProducer.java delete mode 100644 src/main/java/com/codzilla/backend/repository/CoffeeRepository.java create mode 100644 src/main/java/com/codzilla/backend/websocket/ResultWebSocketHandler.java create mode 100644 src/main/java/com/codzilla/backend/websocket/WebSocketConfig.java delete mode 100644 src/test/java/com/codzilla/backend/controller/CoffeeControllerTest.java diff --git a/build.gradle b/build.gradle index b4b4cc0..676d055 100644 --- a/build.gradle +++ b/build.gradle @@ -26,10 +26,12 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.kafka:spring-kafka' + implementation 'org.springframework.boot:spring-boot-starter-websocket' developmentOnly 'org.springframework.boot:spring-boot-devtools' - compileOnly 'org.projectlombok:lombok' // добавить + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql' diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemController.java b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemController.java index 87b915b..aaecf63 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemController.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemController.java @@ -1,16 +1,21 @@ package com.codzilla.backend.controller.Sandbox.problem; import com.codzilla.backend.controller.Sandbox.polygon.CreateProblemRequest; +import com.codzilla.backend.kafka.SubmissionMessage; +import com.codzilla.backend.kafka.SubmissionProducer; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.UUID; + @RestController @RequestMapping("/problems") @RequiredArgsConstructor public class ProblemController { private final ProblemService problemService; + private final SubmissionProducer submissionProducer; @PostMapping("/create") public ResponseEntity createProblem(@RequestBody CreateProblemRequest request) { @@ -18,12 +23,22 @@ public ResponseEntity createProblem(@RequestBody CreateProblemRequest r return ResponseEntity.ok(saved); } +// @PostMapping("/{id}/submit") +// public ResponseEntity submit( +// @PathVariable Long id, +// @RequestParam int languageId, +// @RequestBody String sourceCode) { +// String result = problemService.submitSolution(id, sourceCode, languageId); +// return ResponseEntity.ok(result); +// } + @PostMapping("/{id}/submit") public ResponseEntity submit( @PathVariable Long id, @RequestParam int languageId, @RequestBody String sourceCode) { - String result = problemService.submitSolution(id, sourceCode, languageId); - return ResponseEntity.ok(result); + String submissionId = UUID.randomUUID().toString(); + submissionProducer.send(new SubmissionMessage(submissionId, id, sourceCode, languageId)); + return ResponseEntity.ok(submissionId); } } \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/kafka/SubmissionConsumer.java b/src/main/java/com/codzilla/backend/kafka/SubmissionConsumer.java new file mode 100644 index 0000000..0dd7e4a --- /dev/null +++ b/src/main/java/com/codzilla/backend/kafka/SubmissionConsumer.java @@ -0,0 +1,33 @@ +package com.codzilla.backend.kafka; + +import com.codzilla.backend.controller.Sandbox.problem.ProblemService; +import com.codzilla.backend.websocket.ResultWebSocketHandler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SubmissionConsumer { + + private final ProblemService problemService; + private final ResultWebSocketHandler webSocketHandler; + + @KafkaListener(topics = "submissions", groupId = "codzilla-group") + public void consume(SubmissionMessage message) { + log.info("Received from Kafka: {}", message.getSubmissionId()); + try { + String result = problemService.submitSolution( + message.getProblemId(), + message.getSourceCode(), + message.getLanguageId() + ); + webSocketHandler.sendResult(message.getSubmissionId(), result); + } catch (Exception e) { + log.error("Error processing submission: {}", e.getMessage()); + webSocketHandler.sendResult(message.getSubmissionId(), "Error: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/kafka/SubmissionMessage.java b/src/main/java/com/codzilla/backend/kafka/SubmissionMessage.java new file mode 100644 index 0000000..bf4425b --- /dev/null +++ b/src/main/java/com/codzilla/backend/kafka/SubmissionMessage.java @@ -0,0 +1,15 @@ +package com.codzilla.backend.kafka; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SubmissionMessage { + private String submissionId; + private Long problemId; + private String sourceCode; + private int languageId; +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/kafka/SubmissionProducer.java b/src/main/java/com/codzilla/backend/kafka/SubmissionProducer.java new file mode 100644 index 0000000..4e1bad9 --- /dev/null +++ b/src/main/java/com/codzilla/backend/kafka/SubmissionProducer.java @@ -0,0 +1,19 @@ +package com.codzilla.backend.kafka; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SubmissionProducer { + + private final KafkaTemplate kafkaTemplate; + + public void send(SubmissionMessage message) { + log.info("Sending to Kafka: {}", message.getSubmissionId()); + kafkaTemplate.send("submissions", message.getSubmissionId(), message); + } +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/repository/CoffeeRepository.java b/src/main/java/com/codzilla/backend/repository/CoffeeRepository.java deleted file mode 100644 index 5039e88..0000000 --- a/src/main/java/com/codzilla/backend/repository/CoffeeRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.codzilla.backend.repository; - -import com.codzilla.backend.controller.Coffee; -import org.springframework.data.repository.CrudRepository; - -public interface CoffeeRepository extends CrudRepository { -} diff --git a/src/main/java/com/codzilla/backend/websocket/ResultWebSocketHandler.java b/src/main/java/com/codzilla/backend/websocket/ResultWebSocketHandler.java new file mode 100644 index 0000000..732aebb --- /dev/null +++ b/src/main/java/com/codzilla/backend/websocket/ResultWebSocketHandler.java @@ -0,0 +1,56 @@ +package com.codzilla.backend.websocket; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +public class ResultWebSocketHandler extends TextWebSocketHandler { + + + private final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + + String submissionId = getSubmissionId(session); + if (submissionId != null) { + sessions.put(submissionId, session); + log.info("WebSocket connected: {}", submissionId); + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) { + String submissionId = getSubmissionId(session); + if (submissionId != null) { + sessions.remove(submissionId); + log.info("WebSocket disconnected: {}", submissionId); + } + } + + public void sendResult(String submissionId, String result) { + WebSocketSession session = sessions.get(submissionId); + if (session != null && session.isOpen()) { + try { + session.sendMessage(new TextMessage(result)); + sessions.remove(submissionId); + } catch (Exception e) { + log.error("Failed to send WebSocket message: {}", e.getMessage()); + } + } + } + + private String getSubmissionId(WebSocketSession session) { + String query = session.getUri() != null ? session.getUri().getQuery() : null; + if (query != null && query.startsWith("submissionId=")) { + return query.substring("submissionId=".length()); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/websocket/WebSocketConfig.java b/src/main/java/com/codzilla/backend/websocket/WebSocketConfig.java new file mode 100644 index 0000000..9f4fdcb --- /dev/null +++ b/src/main/java/com/codzilla/backend/websocket/WebSocketConfig.java @@ -0,0 +1,21 @@ +package com.codzilla.backend.websocket; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +@RequiredArgsConstructor +public class WebSocketConfig implements WebSocketConfigurer { + + private final ResultWebSocketHandler resultWebSocketHandler; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(resultWebSocketHandler, "/ws/results") + .setAllowedOrigins("*"); + } +} \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 55ef647..e027dc4 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -6,6 +6,16 @@ polygon.api.key=${POLYGON_KEY} polygon.api.secret=${POLYGON_SECRET} +spring.kafka.bootstrap-servers=${KAFKA_BOOTSTRAP_SERVERS} +spring.kafka.consumer.group-id=codzilla-group +spring.kafka.consumer.auto-offset-reset=earliest +spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer +spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer +spring.kafka.consumer.properties.spring.json.trusted.packages=* + + # ????? ? docker-compose.yml ????? ????? ?? ?????????? ??? ??? #backend: #build: ./backend diff --git a/src/test/java/com/codzilla/backend/controller/CoffeeControllerTest.java b/src/test/java/com/codzilla/backend/controller/CoffeeControllerTest.java deleted file mode 100644 index 7be3348..0000000 --- a/src/test/java/com/codzilla/backend/controller/CoffeeControllerTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.codzilla.backend.controller; - -import com.codzilla.backend.repository.CoffeeRepository; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class CoffeeControllerTest { - - @Autowired - private TestRestTemplate restTemplate; - - @Autowired - private CoffeeRepository coffeeRepository; - - @BeforeEach - void setUp() { - coffeeRepository.deleteAll(); - } - - @Test - void getCoffees_shouldReturnList() { - coffeeRepository.save(new Coffee("Espresso")); - coffeeRepository.save(new Coffee("Latte")); - - ResponseEntity response = restTemplate.getForEntity("/coffee", Coffee[].class); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getBody()).hasSize(2); - } - - @Test - void getCoffee_shouldReturnCoffee_whenExists() { - Coffee saved = coffeeRepository.save(new Coffee("Cappuccino")); - - ResponseEntity response = restTemplate.getForEntity("/coffee/" + saved.getId(), Coffee.class); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - Assertions.assertNotNull(response.getBody()); - assertThat(response.getBody().getName()).isEqualTo("Cappuccino"); - } - - @Test - void postCoffee_shouldCreateAndReturnCoffee() { - Coffee coffee = new Coffee("Mocha"); - - ResponseEntity response = restTemplate.postForEntity("/coffee", coffee, Coffee.class); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - Assertions.assertNotNull(response.getBody()); - assertThat(response.getBody().getName()).isEqualTo("Mocha"); - } - - @Test - void deleteCoffee_shouldRemoveCoffee() { - Coffee saved = coffeeRepository.save(new Coffee("ToDelete")); - - restTemplate.delete("/coffee/" + saved.getId()); - - assertThat(coffeeRepository.findById(saved.getId())).isEmpty(); - } -} \ No newline at end of file From 6f700b20973a3420dfd7b50de45ec5d75010b606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 18 Mar 2026 12:17:33 +0300 Subject: [PATCH 13/16] add : edit ResultWebSocketHandler --- .../websocket/ResultWebSocketHandler.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/codzilla/backend/websocket/ResultWebSocketHandler.java b/src/main/java/com/codzilla/backend/websocket/ResultWebSocketHandler.java index 732aebb..517907f 100644 --- a/src/main/java/com/codzilla/backend/websocket/ResultWebSocketHandler.java +++ b/src/main/java/com/codzilla/backend/websocket/ResultWebSocketHandler.java @@ -2,6 +2,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; @@ -12,21 +13,28 @@ @Component public class ResultWebSocketHandler extends TextWebSocketHandler { - private final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); - @Override - public void afterConnectionEstablished(WebSocketSession session) { + private final ConcurrentHashMap pendingResults = new ConcurrentHashMap<>(); + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { String submissionId = getSubmissionId(session); if (submissionId != null) { sessions.put(submissionId, session); log.info("WebSocket connected: {}", submissionId); + + + String pending = pendingResults.remove(submissionId); + if (pending != null) { + session.sendMessage(new TextMessage(pending)); + sessions.remove(submissionId); + } } } @Override - public void afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) { + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { String submissionId = getSubmissionId(session); if (submissionId != null) { sessions.remove(submissionId); @@ -43,6 +51,10 @@ public void sendResult(String submissionId, String result) { } catch (Exception e) { log.error("Failed to send WebSocket message: {}", e.getMessage()); } + } else { + + log.info("Session not ready, caching result for: {}", submissionId); + pendingResults.put(submissionId, result); } } From f1cb85bef99d7ec537200139cac4bad9591b6a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 18 Mar 2026 12:53:45 +0300 Subject: [PATCH 14/16] add : submission logic - codes --> binary --> db --> judge0 --> kafka --> ans --- .../backend/kafka/SubmissionConsumer.java | 4 ++ .../backend/submission/Submission.java | 26 ++++++++++ .../submission/SubmissionController.java | 23 +++++++++ .../submission/SubmissionRepository.java | 6 +++ .../backend/submission/SubmissionService.java | 50 +++++++++++++++++++ 5 files changed, 109 insertions(+) create mode 100644 src/main/java/com/codzilla/backend/submission/Submission.java create mode 100644 src/main/java/com/codzilla/backend/submission/SubmissionController.java create mode 100644 src/main/java/com/codzilla/backend/submission/SubmissionRepository.java create mode 100644 src/main/java/com/codzilla/backend/submission/SubmissionService.java diff --git a/src/main/java/com/codzilla/backend/kafka/SubmissionConsumer.java b/src/main/java/com/codzilla/backend/kafka/SubmissionConsumer.java index 0dd7e4a..eeb6d84 100644 --- a/src/main/java/com/codzilla/backend/kafka/SubmissionConsumer.java +++ b/src/main/java/com/codzilla/backend/kafka/SubmissionConsumer.java @@ -1,6 +1,7 @@ package com.codzilla.backend.kafka; import com.codzilla.backend.controller.Sandbox.problem.ProblemService; +import com.codzilla.backend.submission.SubmissionService; import com.codzilla.backend.websocket.ResultWebSocketHandler; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -14,6 +15,7 @@ public class SubmissionConsumer { private final ProblemService problemService; private final ResultWebSocketHandler webSocketHandler; + private final SubmissionService submissionService; @KafkaListener(topics = "submissions", groupId = "codzilla-group") public void consume(SubmissionMessage message) { @@ -24,9 +26,11 @@ public void consume(SubmissionMessage message) { message.getSourceCode(), message.getLanguageId() ); + submissionService.updateStatus(message.getSubmissionId(), result); webSocketHandler.sendResult(message.getSubmissionId(), result); } catch (Exception e) { log.error("Error processing submission: {}", e.getMessage()); + submissionService.updateStatus(message.getSubmissionId(), "Error: " + e.getMessage()); webSocketHandler.sendResult(message.getSubmissionId(), "Error: " + e.getMessage()); } } diff --git a/src/main/java/com/codzilla/backend/submission/Submission.java b/src/main/java/com/codzilla/backend/submission/Submission.java new file mode 100644 index 0000000..c5c0227 --- /dev/null +++ b/src/main/java/com/codzilla/backend/submission/Submission.java @@ -0,0 +1,26 @@ +package com.codzilla.backend.submission; + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name = "submissions") +public class Submission { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private Long problemId; + + private int languageId; + + @Lob + @Column(columnDefinition = "BYTEA") + private byte[] sourceCode; // бинарник исходника + + private String status; // PENDING, ACCEPTED, WRONG_ANSWER, etc. + + private String submissionUuid; // для WebSocket +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/submission/SubmissionController.java b/src/main/java/com/codzilla/backend/submission/SubmissionController.java new file mode 100644 index 0000000..ecca52d --- /dev/null +++ b/src/main/java/com/codzilla/backend/submission/SubmissionController.java @@ -0,0 +1,23 @@ +package com.codzilla.backend.submission; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/submissions") +@RequiredArgsConstructor +public class SubmissionController { + + private final SubmissionService submissionService; + + @PostMapping("/upload") + public ResponseEntity upload( + @RequestParam Long problemId, + @RequestParam int languageId, + @RequestParam MultipartFile file) throws Exception { + String submissionUuid = submissionService.upload(problemId, languageId, file); + return ResponseEntity.ok(submissionUuid); + } +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/submission/SubmissionRepository.java b/src/main/java/com/codzilla/backend/submission/SubmissionRepository.java new file mode 100644 index 0000000..4817a9f --- /dev/null +++ b/src/main/java/com/codzilla/backend/submission/SubmissionRepository.java @@ -0,0 +1,6 @@ +package com.codzilla.backend.submission; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SubmissionRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/submission/SubmissionService.java b/src/main/java/com/codzilla/backend/submission/SubmissionService.java new file mode 100644 index 0000000..881fb5c --- /dev/null +++ b/src/main/java/com/codzilla/backend/submission/SubmissionService.java @@ -0,0 +1,50 @@ +package com.codzilla.backend.submission; + +import com.codzilla.backend.kafka.SubmissionMessage; +import com.codzilla.backend.kafka.SubmissionProducer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.UUID; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SubmissionService { + + private final SubmissionRepository submissionRepository; + private final SubmissionProducer submissionProducer; + + public String upload(Long problemId, int languageId, MultipartFile file) throws Exception { + // 1. читаем байты файла + byte[] bytes = file.getBytes(); + + // 2. сохраняем в БД + String submissionUuid = UUID.randomUUID().toString(); + Submission submission = new Submission(); + submission.setProblemId(problemId); + submission.setLanguageId(languageId); + submission.setSourceCode(bytes); + submission.setStatus("PENDING"); + submission.setSubmissionUuid(submissionUuid); + submissionRepository.save(submission); + + // 3. отправляем в Kafka (байты → String) + String sourceCode = new String(bytes); + submissionProducer.send(new SubmissionMessage(submissionUuid, problemId, sourceCode, languageId)); + + return submissionUuid; + } + + public void updateStatus(String submissionUuid, String status) { + submissionRepository.findAll().stream() + .filter(s -> submissionUuid.equals(s.getSubmissionUuid())) + .findFirst() + .ifPresent(s -> { + s.setStatus(status); + submissionRepository.save(s); + }); + } +} \ No newline at end of file From 8d1ef79bea48c41f52757e811b89cc17e78a715e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 18 Mar 2026 13:02:33 +0300 Subject: [PATCH 15/16] add : submission logic - codes --> binary --> db --> judge0 --> kafka --> ans --- .../com/codzilla/backend/submission/Submission.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/codzilla/backend/submission/Submission.java b/src/main/java/com/codzilla/backend/submission/Submission.java index c5c0227..36f6fef 100644 --- a/src/main/java/com/codzilla/backend/submission/Submission.java +++ b/src/main/java/com/codzilla/backend/submission/Submission.java @@ -7,20 +7,16 @@ @Entity @Table(name = "submissions") public class Submission { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long problemId; - private int languageId; - @Lob @Column(columnDefinition = "BYTEA") - private byte[] sourceCode; // бинарник исходника - - private String status; // PENDING, ACCEPTED, WRONG_ANSWER, etc. + private byte[] sourceCode; - private String submissionUuid; // для WebSocket + private String status; + private String submissionUuid; } \ No newline at end of file From 4b42bd6caa492eed9cd6d5c045e9434ce86ca215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=9A=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 18 Mar 2026 13:20:38 +0300 Subject: [PATCH 16/16] add : packege test --- .../Sandbox/problem/ProblemService.java | 42 ++++++++++++------- .../java/com/codzilla/backend/test/Test.java | 23 ++++++++++ .../codzilla/backend/test/TestRepository.java | 8 ++++ 3 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/codzilla/backend/test/Test.java create mode 100644 src/main/java/com/codzilla/backend/test/TestRepository.java diff --git a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java index 3085b38..2e75d2c 100644 --- a/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java +++ b/src/main/java/com/codzilla/backend/controller/Sandbox/problem/ProblemService.java @@ -4,55 +4,67 @@ import com.codzilla.backend.controller.Sandbox.polygon.CreateProblemRequest; import com.codzilla.backend.controller.Sandbox.polygon.PolygonClient; import com.codzilla.backend.controller.Sandbox.polygon.PolygonProblem; +import com.codzilla.backend.test.Test; +import com.codzilla.backend.test.TestRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; - -@Slf4j @Service @RequiredArgsConstructor +@Slf4j public class ProblemService { private final PolygonClient polygonClient; private final ProblemRepository problemRepository; private final Judge0Client judge0Client; - + private final TestRepository testRepository; public Problem createProblem(CreateProblemRequest request) { String polygonId = polygonClient.createProblem(request.getName()); - var tests = request.getTests(); - for (int i = 0; i < tests.size(); i++) { - var test = tests.get(i); - polygonClient.saveTest(polygonId, i + 1, test.getInput(), test.getOutput()); - } - Problem problem = new Problem(); problem.setPolygonToken(polygonId); problem.setType(request.getType()); problem.setLevel(request.getLevel()); + Problem saved = problemRepository.save(problem); - return problemRepository.save(problem); + // сохраняем тесты в Polygon И в нашу БД + var tests = request.getTests(); + for (int i = 0; i < tests.size(); i++) { + var t = tests.get(i); + polygonClient.saveTest(polygonId, i + 1, t.getInput(), t.getOutput()); + + Test test = new Test(); + test.setProblemId(saved.getId()); + test.setTestIndex(i + 1); + test.setInput(t.getInput()); + test.setExpectedOutput(t.getOutput()); + testRepository.save(test); + } + + return saved; } public String submitSolution(Long problemId, String sourceCode, int languageId) { Problem problem = problemRepository.findById(problemId) .orElseThrow(() -> new RuntimeException("Problem not found: " + problemId)); - PolygonProblem polygonProblem = polygonClient.getProblemTests(problem.getPolygonToken()); - + // берём тесты из НАШЕЙ БД + List tests = testRepository.findByProblemIdOrderByTestIndex(problemId); + if (tests.isEmpty()) { + throw new RuntimeException("No tests found for problem: " + problemId); + } List results = new ArrayList<>(); - for (var test : polygonProblem.getResult()) { + for (Test test : tests) { String verdict = judge0Client.submit(sourceCode, languageId, test.getInput()); - results.add("Test " + test.getIndex() + ": " + verdict); + results.add("Test " + test.getTestIndex() + ": " + verdict); if (!"Accepted".equals(verdict)) break; } return String.join("\n", results); } - } \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/test/Test.java b/src/main/java/com/codzilla/backend/test/Test.java new file mode 100644 index 0000000..0049da1 --- /dev/null +++ b/src/main/java/com/codzilla/backend/test/Test.java @@ -0,0 +1,23 @@ +package com.codzilla.backend.test; + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name = "tests") +public class Test { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private Long problemId; + private int testIndex; + + @Column(columnDefinition = "TEXT") + private String input; + + @Column(columnDefinition = "TEXT") + private String expectedOutput; +} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/test/TestRepository.java b/src/main/java/com/codzilla/backend/test/TestRepository.java new file mode 100644 index 0000000..2c38167 --- /dev/null +++ b/src/main/java/com/codzilla/backend/test/TestRepository.java @@ -0,0 +1,8 @@ +package com.codzilla.backend.test; + +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + +public interface TestRepository extends JpaRepository { + List findByProblemIdOrderByTestIndex(Long problemId); +} \ No newline at end of file