diff --git a/src/main/java/ru/oa2/lti/ApplicationService.java b/src/main/java/ru/oa2/lti/ApplicationService.java index 9835836..719bba1 100644 --- a/src/main/java/ru/oa2/lti/ApplicationService.java +++ b/src/main/java/ru/oa2/lti/ApplicationService.java @@ -2,6 +2,7 @@ package ru.oa2.lti; import jakarta.transaction.Transactional; import org.springframework.stereotype.Service; +import ru.oa2.lti.infrastructure.runner.Runner; import ru.oa2.lti.model.LtiLogin; import ru.oa2.lti.model.TaskData; import ru.oa2.lti.repository.LMSContentRepository; @@ -19,11 +20,14 @@ public class ApplicationService { private final JwtService jwtService; private final LMSContentRepository lmsContentRepository; + private final Runner runner; public ApplicationService(JwtService jwtService, - LMSContentRepository lmsContentRepository) { + LMSContentRepository lmsContentRepository, + Runner runner) { this.jwtService = jwtService; this.lmsContentRepository = lmsContentRepository; + this.runner = runner; } public Payload getPayload(LtiLogin ltiLogin) { @@ -41,7 +45,10 @@ public class ApplicationService { //TODO добавить версию в Task и выбирать самую старшую и опубликованную TaskData data = tasks.stream().findFirst().get().getData(); - //TODO запуск initScript + if (!runner.run(contextId, data.getInitScript())) { + return "Init script FAILED"; + } + return data.getDescription(); } else { return "Not Page"; @@ -50,7 +57,6 @@ public class ApplicationService { public String saveResult(String body) { - //TODO return "{\n" + " \"success\": true,\n" + " \"message\": \"Результат успешно получен и обработан\",\n" + diff --git a/src/main/java/ru/oa2/lti/controller/LoginController.java b/src/main/java/ru/oa2/lti/controller/LaunchController.java similarity index 79% rename from src/main/java/ru/oa2/lti/controller/LoginController.java rename to src/main/java/ru/oa2/lti/controller/LaunchController.java index 4eb5f3f..96fc49f 100644 --- a/src/main/java/ru/oa2/lti/controller/LoginController.java +++ b/src/main/java/ru/oa2/lti/controller/LaunchController.java @@ -1,31 +1,28 @@ package ru.oa2.lti.controller; -import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; import ru.oa2.lti.ApplicationService; +import ru.oa2.lti.infrastructure.LMSService; import ru.oa2.lti.model.LtiLogin; import ru.oa2.lti.service.jwt.Payload; -import ru.oa2.lti.service.jwt.TokenService; @Slf4j @Controller @RequestMapping("/tool/lti") -public class LoginController { +public class LaunchController { private final ApplicationService service; - private final TokenService tokenService; + private final LMSService lmsService; - public LoginController(ApplicationService applicationService, - TokenService tokenService) { + public LaunchController(ApplicationService applicationService, + LMSService lmsService) { this.service = applicationService; - this.tokenService = tokenService; + this.lmsService = lmsService; } @ResponseBody @@ -36,9 +33,7 @@ public class LoginController { @RequestParam("lti_deployment_id") String ltiDeploymentId, @RequestParam("lti_message_hint") String ltiMessageHint, @RequestParam("target_link_uri") String targetLinkUri, - HttpServletRequest request, - RedirectAttributes redirectAttributes, - ServletResponse servletResponse) { + HttpServletRequest request) { var ltiLogin = LtiLogin.builder() .clientId(clientId) @@ -62,8 +57,7 @@ public class LoginController { session.setAttribute("payload", payload); if (payload.getContextId() != null) { - var body = tokenService.ltiAuth(ltiMessageHint, iss, loginHint); - return body.getBody(); + return lmsService.ltiAuth(ltiMessageHint, iss, loginHint); } return "redirect:/tool/lti/select"; @@ -85,8 +79,6 @@ public class LoginController { } model.addAttribute("description", data); - model.addAttribute("codeTitle", "Dockerfile:"); - model.addAttribute("inputCode", "FROM ..."); } return "task"; diff --git a/src/main/java/ru/oa2/lti/controller/ResultController.java b/src/main/java/ru/oa2/lti/controller/ResultController.java index 019953f..34dc8e0 100644 --- a/src/main/java/ru/oa2/lti/controller/ResultController.java +++ b/src/main/java/ru/oa2/lti/controller/ResultController.java @@ -3,21 +3,17 @@ package ru.oa2.lti.controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.web.client.RestClientBuilderConfigurer; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestClient; import ru.oa2.lti.ApplicationService; import ru.oa2.lti.model.LtiLogin; import ru.oa2.lti.service.jwt.JwtService; import ru.oa2.lti.service.jwt.TokenService; import ru.oa2.lti.service.results.ResultService; -import java.util.UUID; - @Slf4j @RestController @RequestMapping("/tool/lti/result") @@ -47,6 +43,7 @@ public class ResultController { HttpSession session = req.getSession(false); if (session == null) return ResponseEntity.status(401).build(); + //TODO запуск скрипта проверки var ltiLogin = (LtiLogin) session.getAttribute("lti_login"); var assessToken = tokenService.exchangeForAccessToken( ltiLogin.getClientId()); diff --git a/src/main/java/ru/oa2/lti/controller/TODO.md b/src/main/java/ru/oa2/lti/controller/TODO.md deleted file mode 100644 index ceac1d6..0000000 --- a/src/main/java/ru/oa2/lti/controller/TODO.md +++ /dev/null @@ -1,5 +0,0 @@ - -```shell -curl -XPOST http://localhost:9999/tool/lti/login \ - -d 'client_id=27e2d42d-d218-4ab9-b063-85e3ec87ec8f&iss=https%3A%2F%2Flocalhost&login_hint=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImU5ODA3YTg2LTVhNWQtNDI0MC1hYjI1LWNhYTUyZDliZjUxOSJ9.eyJkZXBsb3ltZW50S2V5IjoxLCJkZXBsb3ltZW50SWQiOiIyNzE1ZDA0NS03N2M1LTRkODgtYTFlNC05MmQ3MjFmY2M1NGIiLCJjb250ZXh0S2V5IjoxLCJjb250ZXh0SWQiOiJlMmE4ZGQxMy04MWJhLTQwMTEtODgwNi0wMjA5NGZkZjg5ZjMiLCJjb3Vyc2VhZG1pbiI6ZmFsc2UsImNvYWNoIjpmYWxzZSwicGFydGljaXBhbnQiOnRydWV9.vgPLHQA9scdED3_OwOy46h6VmzpN4arIHY3-YDBdhH4kuEqeCOjjtFbGdDqauKoQBTSVo4UvoK4JQLMhak6qsFchCj54mPob8jbaKLd0GnO_jY0sR609Nrk7Muq7cki_4PjVMX8TTHp-VYlSHjVxQH_z_D5Wld27J95z4qJjRU59GmvLGDqdLyerVVBO-zaavYsUbEEiAxoX3hmytxrarmJ7OHpxufNOeXzZ0DSGUmU5ycuTAqxODaHO1Y4rQM6XlvSfDh_TmXP8QEkatlp2cdjRpNWyOdUW_hZfbtkqukwt1ZP7KEgWzNI3vivpBjm2xfUG2YLwXPJqHa47NQgvsQ<i_deployment_id=2715d045-77c5-4d88-a1e4-92d721fcc54b<i_message_hint=1802240&target_link_uri=https%3A%2F%2Fopenolat.local%2Ftool' -``` \ No newline at end of file diff --git a/src/main/java/ru/oa2/lti/infrastructure/LMSService.java b/src/main/java/ru/oa2/lti/infrastructure/LMSService.java new file mode 100644 index 0000000..b408dda --- /dev/null +++ b/src/main/java/ru/oa2/lti/infrastructure/LMSService.java @@ -0,0 +1,6 @@ +package ru.oa2.lti.infrastructure; + +public interface LMSService { + + String ltiAuth(String ltiLoginHint, String iss, String loginHint); +} diff --git a/src/main/java/ru/oa2/lti/infrastructure/LMSServiceImpl.java b/src/main/java/ru/oa2/lti/infrastructure/LMSServiceImpl.java new file mode 100644 index 0000000..bfe35f8 --- /dev/null +++ b/src/main/java/ru/oa2/lti/infrastructure/LMSServiceImpl.java @@ -0,0 +1,40 @@ +package ru.oa2.lti.infrastructure; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; +import ru.oa2.lti.config.AppProperties; + +@Slf4j +@Component +public class LMSServiceImpl implements LMSService { + + private final RestClient restClient; + private final AppProperties appProperties; + + public LMSServiceImpl(RestClient restClient, AppProperties appProperties) { + this.restClient = restClient; + this.appProperties = appProperties; + } + + @Override + public String ltiAuth(String ltiLoginHint, String iss, String loginHint) { + var result = restClient + .get() + .uri("/lti/auth?" + + "state=start" + + "&code=code1" + + "&iss=" + iss + + "&login_hint=" + loginHint + + "&redirect_uri=" + appProperties.lmsUrl() + "/tool/lti/redirect" + + "<i_message_hint=" + ltiLoginHint) + .retrieve(); + + ResponseEntity body = result.toEntity(String.class); + if (log.isDebugEnabled()) { + log.debug("lti/auth RESPONSE: {}", body); + } + return body.getBody(); + } +} diff --git a/src/main/java/ru/oa2/lti/infrastructure/runner/Runner.java b/src/main/java/ru/oa2/lti/infrastructure/runner/Runner.java new file mode 100644 index 0000000..3d9e2e5 --- /dev/null +++ b/src/main/java/ru/oa2/lti/infrastructure/runner/Runner.java @@ -0,0 +1,10 @@ +package ru.oa2.lti.infrastructure.runner; + +import java.util.UUID; + +public interface Runner { + + boolean run(UUID userId, String script); + + boolean check(UUID userId, String script); +} diff --git a/src/main/java/ru/oa2/lti/infrastructure/runner/RunnerImpl.java b/src/main/java/ru/oa2/lti/infrastructure/runner/RunnerImpl.java new file mode 100644 index 0000000..116d92c --- /dev/null +++ b/src/main/java/ru/oa2/lti/infrastructure/runner/RunnerImpl.java @@ -0,0 +1,77 @@ +package ru.oa2.lti.infrastructure.runner; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +@Slf4j +@Component +public class RunnerImpl implements Runner { + + private final String TEMP_DIR = "./temp/"; + + @Override + public boolean run(UUID userId, String script) { + try { + return runScript(userId, script); + } catch (Exception ex) { + log.error(ex.getMessage()); + return false; + } + } + + //TODO добавить вывод результата/интерпретации + @Override + public boolean check(UUID userId, String script) { + try { + return runScript(userId, script); + } catch (Exception ex) { + log.error(ex.getMessage()); + return false; + } + } + + private boolean runScript(UUID userId, String script) throws Exception { + + var processBuilder = new ProcessBuilder(); + processBuilder.command("bash", saveFile(userId, script)); + + var process = processBuilder.start(); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream()) + ); + + StringBuilder output = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + log.info(output.toString()); + + int exitCode = process.waitFor(); + return exitCode == 0; + } + + private String saveFile(UUID userId, String script) throws IOException { + Path path = Paths.get(TEMP_DIR + userId.toString()); + + if (!Files.exists(path)) { + Files.createDirectories(path); + } + var tm = System.currentTimeMillis(); + var scriptPath = Files.write( + Paths.get(TEMP_DIR + userId + "/" + tm), + script.getBytes(StandardCharsets.UTF_8)); + + return scriptPath.toString(); + } +} diff --git a/src/main/java/ru/oa2/lti/service/jwt/TokenService.java b/src/main/java/ru/oa2/lti/service/jwt/TokenService.java index 3cb85ea..f435282 100644 --- a/src/main/java/ru/oa2/lti/service/jwt/TokenService.java +++ b/src/main/java/ru/oa2/lti/service/jwt/TokenService.java @@ -16,11 +16,9 @@ import java.util.Map; public class TokenService { private final RestTemplate restTemplate = new RestTemplate(); - private final RestClient restClient; private final AppProperties appProperties; - public TokenService(RestClient restClient, AppProperties appProperties) { - this.restClient = restClient; + public TokenService(AppProperties appProperties) { this.appProperties = appProperties; } @@ -65,24 +63,4 @@ public class TokenService { throw new RuntimeException("Не удалось получить access token", e); } } - - public ResponseEntity ltiAuth(String ltiLoginHint, String iss, String loginHint) { - var result = restClient - .get() - .uri("/lti/auth?" + - "state=start" + - "&code=code1" + - "&iss=" + iss + - "&login_hint=" + loginHint + - "&redirect_uri=" + appProperties.lmsUrl() + "/tool/lti/redirect" + - "<i_message_hint=" + ltiLoginHint) - .retrieve(); - - ResponseEntity body = result.toEntity(String.class); - if (log.isDebugEnabled()) { - log.debug("lti/auth RESPONSE: {}", body); - } - return body; - } - } \ No newline at end of file diff --git a/src/main/java/ru/oa2/lti/service/results/dto/ResultResponse.java b/src/main/java/ru/oa2/lti/service/results/dto/ResultResponse.java new file mode 100644 index 0000000..631ec06 --- /dev/null +++ b/src/main/java/ru/oa2/lti/service/results/dto/ResultResponse.java @@ -0,0 +1,6 @@ +package ru.oa2.lti.service.results.dto; + +public record ResultResponse( + String status +) { +} diff --git a/src/main/resources/static/tool/lti/js/main.js b/src/main/resources/static/tool/lti/js/main.js new file mode 100644 index 0000000..cd2a429 --- /dev/null +++ b/src/main/resources/static/tool/lti/js/main.js @@ -0,0 +1,45 @@ +document.getElementById('submitBtn').addEventListener('click', function () { + // Показываем, что загрузка началась + const statusContainer = document.getElementById('statusContainer'); + const icon = document.getElementById('icon'); + const statusLabel = document.getElementById('statusLabel'); + + // Получаем данные пользователя + const contextId = document.getElementById('contextId')?.value || 'unknown'; + const participantId = document.getElementById('participantId')?.value || 'unknown'; + + const payload = { + contextId: contextId, + participantId: participantId + }; + + fetch('/tool/lti/result/docker', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }) + .then(response => { + if (!response.ok) throw new Error(`Ошибка сети: ${response.status}`); + return response.json(); + }) + .then(data => { + // Показываем статус + statusContainer.style.display = 'flex'; + + if (data.status === 'success') { + icon.className = 'fa-solid fa-check-circle status-success'; + statusLabel.textContent = 'Success'; + statusLabel.className = 'status-label status-success'; + } else { + icon.className = 'fa-solid fa-xmark-circle status-failed'; + statusLabel.textContent = 'Failed'; + statusLabel.className = 'status-label status-failed'; + } + }) + .catch(() => { + statusContainer.style.display = 'flex'; + icon.className = 'fa-solid fa-xmark-circle status-failed'; + statusLabel.textContent = 'Failed'; + statusLabel.className = 'status-label status-failed'; + }); +}); \ No newline at end of file diff --git a/src/main/resources/templates/task.html b/src/main/resources/templates/task.html index 8b6c0ce..8fb2a9c 100644 --- a/src/main/resources/templates/task.html +++ b/src/main/resources/templates/task.html @@ -4,182 +4,116 @@ + + +
-
- -
-

Условие задачи

-
-
-
- - -
- -
-

Введите решение

-
-
- -
- -
-
- - -
-

Результат выполнения

-
-
Вывод программы:
-
- - Результат выполнения появится здесь... -
-
- -
-
-
+ +
+
+
+ + + +
- + \ No newline at end of file diff --git a/src/test/java/ru/oa2/lti/infrastructure/RunnerTest.java b/src/test/java/ru/oa2/lti/infrastructure/RunnerTest.java new file mode 100644 index 0000000..aaf6d65 --- /dev/null +++ b/src/test/java/ru/oa2/lti/infrastructure/RunnerTest.java @@ -0,0 +1,32 @@ +package ru.oa2.lti.infrastructure; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import ru.oa2.lti.infrastructure.runner.Runner; +import ru.oa2.lti.infrastructure.runner.RunnerImpl; + +import java.util.UUID; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + + +@SpringBootTest(classes = {RunnerImpl.class}) +public class RunnerTest { + + @Autowired + Runner runner; + + @Test + public void runnerTest() { + + var script = "#!/bin/bash\n" + + "echo \"Запуск развёртывания...\"\n" + + "sleep 2\n" + + "echo \"Развёртывание завершено.\""; + + var result = runner.run(UUID.randomUUID(), script); + + assertThat(result).isEqualTo(true); + } +}