refactoring, добавление портов, разделение домена и реализацию
This commit is contained in:
parent
bf68f3fe0a
commit
498ba86305
|
|
@ -5,80 +5,63 @@ import jakarta.servlet.http.HttpSession;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import ru.oa2.lti.application.infrastructure.lms.LMSService;
|
import ru.oa2.lti.application.usecase.lti.HandleLtiRedirectUseCase;
|
||||||
import ru.oa2.lti.application.service.history.HistoryService;
|
import ru.oa2.lti.application.usecase.lti.ProcessLtiLoginUseCase;
|
||||||
import ru.oa2.lti.application.service.jwt.JwtService;
|
|
||||||
import ru.oa2.lti.domain.model.auth.LtiLogin;
|
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
* Входная точка в external tool по протоколу LTI 1.3.
|
||||||
Входная точка в external tool по протоколу LTI 1.3
|
*
|
||||||
|
* Контроллер только обрабатывает HTTP запросы и делегирует
|
||||||
#TODO расписать процесс и добавить линк на доку
|
* бизнес-логику в Use Cases.
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/tool/lti")
|
@RequestMapping("/tool/lti")
|
||||||
public class LaunchController {
|
public class LaunchController {
|
||||||
|
|
||||||
private final JwtService jwtService;
|
private final ProcessLtiLoginUseCase processLtiLoginUseCase;
|
||||||
private final LMSService lmsService;
|
private final HandleLtiRedirectUseCase handleLtiRedirectUseCase;
|
||||||
private final HistoryService historyService;
|
|
||||||
|
|
||||||
public LaunchController(JwtService jwtService,
|
public LaunchController(ProcessLtiLoginUseCase processLtiLoginUseCase,
|
||||||
LMSService lmsService,
|
HandleLtiRedirectUseCase handleLtiRedirectUseCase) {
|
||||||
HistoryService historyService) {
|
this.processLtiLoginUseCase = processLtiLoginUseCase;
|
||||||
this.jwtService = jwtService;
|
this.handleLtiRedirectUseCase = handleLtiRedirectUseCase;
|
||||||
this.lmsService = lmsService;
|
|
||||||
this.historyService = historyService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public String login(@RequestParam("client_id") String clientId,
|
public String login(@RequestParam("client_id") String clientId,
|
||||||
@RequestParam("iss") String iss,
|
@RequestParam("iss") String iss,
|
||||||
@RequestParam("login_hint") String loginHint,
|
@RequestParam("login_hint") String loginHint,
|
||||||
@RequestParam("lti_deployment_id") String ltiDeploymentId,
|
@RequestParam("lti_deployment_id") String ltiDeploymentId,
|
||||||
@RequestParam("lti_message_hint") String ltiMessageHint,
|
@RequestParam("lti_message_hint") String ltiMessageHint,
|
||||||
@RequestParam("target_link_uri") String targetLinkUri,
|
@RequestParam("target_link_uri") String targetLinkUri,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
|
|
||||||
var ltiLogin = LtiLogin.builder()
|
// Создаём команду для Use Case
|
||||||
.clientId(clientId)
|
var command = new ProcessLtiLoginUseCase.LtiLoginCommand(
|
||||||
.iss(iss)
|
clientId, iss, loginHint, ltiDeploymentId, ltiMessageHint, targetLinkUri
|
||||||
.loginHint(loginHint)
|
);
|
||||||
.ltiDeploymentId(ltiDeploymentId)
|
|
||||||
.ltiMessageHint(ltiMessageHint)
|
|
||||||
.targetLinkUri(targetLinkUri)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
var payload = jwtService.getPayload(ltiLogin.getLoginHint());
|
// Выполняем бизнес-логику через Use Case
|
||||||
|
var result = processLtiLoginUseCase.execute(command);
|
||||||
|
|
||||||
// 4. Сохранить access token в сессии
|
// Сохраняем данные в сессии (HTTP-специфичная логика)
|
||||||
HttpSession session = request.getSession();
|
HttpSession session = request.getSession();
|
||||||
session.setAttribute("lti_login", ltiLogin);
|
session.setAttribute("lti_login", result.ltiLogin());
|
||||||
session.setAttribute("lti_message_hint", Long.valueOf(ltiMessageHint));
|
session.setAttribute("lti_message_hint", Long.valueOf(ltiMessageHint));
|
||||||
session.setAttribute("lti_context_id", payload.getContextId());
|
session.setAttribute("lti_context_id", result.payload().getContextId());
|
||||||
session.setAttribute("lti_user_id", ltiLogin.getClientId());
|
session.setAttribute("lti_user_id", clientId);
|
||||||
session.setAttribute("payload", payload);
|
session.setAttribute("payload", result.payload());
|
||||||
|
|
||||||
// Логирование события входа
|
// Возвращаем результат
|
||||||
historyService.logAction(
|
if (result.requiresSelection()) {
|
||||||
payload.getDeploymentId(),
|
return "redirect:/tool/lti/select";
|
||||||
payload.getContextId(),
|
}
|
||||||
"LTI_LOGIN",
|
return result.redirectResponse();
|
||||||
String.format("User login via LTI 1.3, clientId=%s", clientId)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (payload.getContextId() != null) {
|
|
||||||
return lmsService.ltiAuth(ltiMessageHint, iss, loginHint);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "redirect:/tool/lti/select";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/select")
|
@GetMapping("/select")
|
||||||
public String select(Model model) {
|
public String select(Model model) {
|
||||||
//TODO доработать форму выбора существующих заданий
|
|
||||||
return "select";
|
return "select";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,21 +70,14 @@ public class LaunchController {
|
||||||
@RequestParam("state") String state,
|
@RequestParam("state") String state,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
|
|
||||||
|
// Выполняем бизнес-логику через Use Case
|
||||||
|
var result = handleLtiRedirectUseCase.execute(idToken, state);
|
||||||
|
|
||||||
|
// Сохраняем данные в сессии (HTTP-специфичная логика)
|
||||||
HttpSession session = request.getSession();
|
HttpSession session = request.getSession();
|
||||||
session.setAttribute("id_token", idToken);
|
session.setAttribute("id_token", idToken);
|
||||||
session.setAttribute("state", state);
|
session.setAttribute("state", state);
|
||||||
|
|
||||||
// Декодируем id_token для получения информации о контексте
|
return "redirect:" + result.redirectUrl();
|
||||||
var idTokenPayload = jwtService.getTokenPayload(idToken);
|
|
||||||
|
|
||||||
// Логирование перенаправления после авторизации
|
|
||||||
historyService.logAction(
|
|
||||||
idTokenPayload.getDeploymentId(),
|
|
||||||
idTokenPayload.getContext() != null ? idTokenPayload.getContext().id() : null,
|
|
||||||
"LTI_REDIRECT",
|
|
||||||
"User redirected after OAuth authentication"
|
|
||||||
);
|
|
||||||
|
|
||||||
return "redirect:/tool/lti/task";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,14 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import ru.oa2.lti.application.service.history.HistoryService;
|
import ru.oa2.lti.domain.port.out.HistoryRepository;
|
||||||
import ru.oa2.lti.application.service.task.TaskService;
|
import ru.oa2.lti.application.service.task.TaskService;
|
||||||
import ru.oa2.lti.domain.model.ResultRequest;
|
import ru.oa2.lti.application.dto.auth.LtiLogin;
|
||||||
import ru.oa2.lti.domain.model.auth.LtiLogin;
|
import ru.oa2.lti.application.dto.auth.Payload;
|
||||||
import ru.oa2.lti.domain.model.auth.Payload;
|
import ru.oa2.lti.application.dto.result.ResultResponse;
|
||||||
import ru.oa2.lti.domain.model.results.ResultResponse;
|
import ru.oa2.lti.application.dto.task.CheckTaskRequest;
|
||||||
import ru.oa2.lti.domain.model.task.RequestUpdateTask;
|
import ru.oa2.lti.application.dto.task.RequestUpdateTask;
|
||||||
import ru.oa2.lti.domain.model.task.TaskData;
|
import ru.oa2.lti.application.dto.task.TaskData;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
@ -34,11 +34,11 @@ POST /submit - отправка на автоматическую проверк
|
||||||
public class TaskController {
|
public class TaskController {
|
||||||
|
|
||||||
private final TaskService taskService;
|
private final TaskService taskService;
|
||||||
private final HistoryService historyService;
|
private final HistoryRepository historyRepository;
|
||||||
|
|
||||||
public TaskController(TaskService taskService, HistoryService historyService) {
|
public TaskController(TaskService taskService, HistoryRepository historyRepository) {
|
||||||
this.taskService = taskService;
|
this.taskService = taskService;
|
||||||
this.historyService = historyService;
|
this.historyRepository = historyRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
|
@ -52,7 +52,7 @@ public class TaskController {
|
||||||
var data = taskService.getTask(payload.getContextId());
|
var data = taskService.getTask(payload.getContextId());
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
historyService.logAction(
|
historyRepository.logAction(
|
||||||
payload.getDeploymentId(),
|
payload.getDeploymentId(),
|
||||||
payload.getContextId(),
|
payload.getContextId(),
|
||||||
"TASK_ERROR",
|
"TASK_ERROR",
|
||||||
|
|
@ -65,7 +65,7 @@ public class TaskController {
|
||||||
model.addAttribute("description", data.getDescription());
|
model.addAttribute("description", data.getDescription());
|
||||||
|
|
||||||
// Логирование просмотра задачи
|
// Логирование просмотра задачи
|
||||||
historyService.logAction(
|
historyRepository.logAction(
|
||||||
payload.getDeploymentId(),
|
payload.getDeploymentId(),
|
||||||
payload.getContextId(),
|
payload.getContextId(),
|
||||||
"TASK_VIEW",
|
"TASK_VIEW",
|
||||||
|
|
@ -93,7 +93,7 @@ public class TaskController {
|
||||||
|
|
||||||
if (payload != null && payload.getCoach()) {
|
if (payload != null && payload.getCoach()) {
|
||||||
// Логирование обновления задачи
|
// Логирование обновления задачи
|
||||||
historyService.logAction(
|
historyRepository.logAction(
|
||||||
payload.getDeploymentId(),
|
payload.getDeploymentId(),
|
||||||
payload.getContextId(),
|
payload.getContextId(),
|
||||||
"TASK_UPDATE",
|
"TASK_UPDATE",
|
||||||
|
|
@ -110,7 +110,7 @@ public class TaskController {
|
||||||
|
|
||||||
// Логирование попытки несанкционированного обновления
|
// Логирование попытки несанкционированного обновления
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
historyService.logAction(
|
historyRepository.logAction(
|
||||||
payload.getDeploymentId(),
|
payload.getDeploymentId(),
|
||||||
payload.getContextId(),
|
payload.getContextId(),
|
||||||
"TASK_UPDATE_DENIED",
|
"TASK_UPDATE_DENIED",
|
||||||
|
|
@ -134,7 +134,7 @@ public class TaskController {
|
||||||
|
|
||||||
// Логирование отправки задачи на проверку
|
// Логирование отправки задачи на проверку
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
historyService.logAction(
|
historyRepository.logAction(
|
||||||
payload.getDeploymentId(),
|
payload.getDeploymentId(),
|
||||||
payload.getContextId(),
|
payload.getContextId(),
|
||||||
"TASK_SUBMIT",
|
"TASK_SUBMIT",
|
||||||
|
|
@ -144,6 +144,6 @@ public class TaskController {
|
||||||
|
|
||||||
return ResponseEntity
|
return ResponseEntity
|
||||||
.accepted()
|
.accepted()
|
||||||
.body(taskService.checkTask(new ResultRequest(ltiLogin.getClientId(), idToken)));
|
.body(taskService.checkTask(new CheckTaskRequest(ltiLogin.getClientId(), idToken)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package ru.oa2.lti.application.domain.carecase;
|
|
||||||
|
|
||||||
public class GetTask {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.auth;
|
package ru.oa2.lti.application.dto.auth;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.auth;
|
package ru.oa2.lti.application.dto.auth;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
"https://purl.imsglobal.org/spec/lti/claim/launch_presentation" : {
|
"https://purl.imsglobal.org/spec/lti/claim/launch_presentation" : {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.auth;
|
package ru.oa2.lti.application.dto.auth;
|
||||||
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.auth;
|
package ru.oa2.lti.application.dto.auth;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.auth;
|
package ru.oa2.lti.application.dto.auth;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.auth;
|
package ru.oa2.lti.application.dto.auth;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.auth;
|
package ru.oa2.lti.application.dto.auth;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.auth;
|
package ru.oa2.lti.application.dto.auth;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.auth;
|
package ru.oa2.lti.application.dto.auth;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.results;
|
package ru.oa2.lti.application.dto.result;
|
||||||
|
|
||||||
public enum GradingType {
|
public enum GradingType {
|
||||||
NotStarted,
|
NotStarted,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.results;
|
package ru.oa2.lti.application.dto.result;
|
||||||
|
|
||||||
public record Lineitems(
|
public record Lineitems(
|
||||||
String id,
|
String id,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.results;
|
package ru.oa2.lti.application.dto.result;
|
||||||
|
|
||||||
public enum ProgressType {
|
public enum ProgressType {
|
||||||
Initialized,
|
Initialized,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.results;
|
package ru.oa2.lti.application.dto.result;
|
||||||
|
|
||||||
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.results;
|
package ru.oa2.lti.application.dto.result;
|
||||||
|
|
||||||
public record ResultResponse(
|
public record ResultResponse(
|
||||||
String status
|
String status
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package ru.oa2.lti.application.dto.task;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO для запроса проверки задачи
|
||||||
|
*/
|
||||||
|
public record CheckTaskRequest(
|
||||||
|
String clientId,
|
||||||
|
String idToken
|
||||||
|
) {}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.task;
|
package ru.oa2.lti.application.dto.task;
|
||||||
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.task;
|
package ru.oa2.lti.application.dto.task;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.task;
|
package ru.oa2.lti.application.dto.task;
|
||||||
|
|
||||||
public enum TaskQueueStatus {
|
public enum TaskQueueStatus {
|
||||||
NEW,
|
NEW,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.task;
|
package ru.oa2.lti.application.dto.task;
|
||||||
|
|
||||||
public record TaskResult(
|
public record TaskResult(
|
||||||
//TODO результат работы(лог), true/false, error:<msg>, оценка
|
//TODO результат работы(лог), true/false, error:<msg>, оценка
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.model.task;
|
package ru.oa2.lti.application.dto.task;
|
||||||
|
|
||||||
public enum TaskType {
|
public enum TaskType {
|
||||||
DOCKER,
|
DOCKER,
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.oa2.lti.application.infrastructure.lms;
|
|
||||||
|
|
||||||
public interface LMSService {
|
|
||||||
|
|
||||||
String ltiAuth(String ltiLoginHint, String iss, String loginHint);
|
|
||||||
|
|
||||||
// Вызывается после LTI-запуска
|
|
||||||
String exchangeForAccessToken(String clientId);
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package ru.oa2.lti.application.infrastructure.runner;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface Runner {
|
|
||||||
|
|
||||||
boolean run(UUID userId, String script);
|
|
||||||
|
|
||||||
boolean check(UUID userId, String script);
|
|
||||||
|
|
||||||
boolean delete(UUID userId, String script);
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
package ru.oa2.lti.application.infrastructure.runner;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import ru.oa2.lti.application.service.history.HistoryService;
|
|
||||||
|
|
||||||
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/";
|
|
||||||
private final HistoryService historyService;
|
|
||||||
|
|
||||||
public RunnerImpl(HistoryService historyService) {
|
|
||||||
this.historyService = historyService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean run(UUID userId, String script) {
|
|
||||||
try {
|
|
||||||
historyService.logAction(null, userId, "SCRIPT_RUN_START", "Init script execution started");
|
|
||||||
boolean result = runScript(userId, script);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
historyService.logAction(null, userId, "SCRIPT_RUN_SUCCESS", "Init script completed successfully");
|
|
||||||
} else {
|
|
||||||
historyService.logAction(null, userId, "SCRIPT_RUN_FAILED", "Init script failed with non-zero exit code");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.error(ex.getMessage());
|
|
||||||
historyService.logAction(null, userId, "SCRIPT_RUN_ERROR", "Init script error: " + ex.getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO добавить вывод результата/интерпретации
|
|
||||||
@Override
|
|
||||||
public boolean check(UUID userId, String script) {
|
|
||||||
try {
|
|
||||||
historyService.logAction(null, userId, "SCRIPT_CHECK_START", "Verification script execution started");
|
|
||||||
boolean result = runScript(userId, script);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
historyService.logAction(null, userId, "SCRIPT_CHECK_SUCCESS", "Verification script completed successfully");
|
|
||||||
} else {
|
|
||||||
historyService.logAction(null, userId, "SCRIPT_CHECK_FAILED", "Verification script failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.error(ex.getMessage());
|
|
||||||
historyService.logAction(null, userId, "SCRIPT_CHECK_ERROR", "Verification script error: " + ex.getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean delete(UUID userId, String script) {
|
|
||||||
//TODO реализовать метод удаления сущностей
|
|
||||||
historyService.logAction(null, userId, "SCRIPT_DELETE_CALLED", "Delete script called (not implemented)");
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
package ru.oa2.lti.application.service.history;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface HistoryService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Логирование события
|
|
||||||
*
|
|
||||||
* @param deploymentId ID деплоймента LMS
|
|
||||||
* @param contentUuid UUID контента
|
|
||||||
* @param message Сообщение о событии
|
|
||||||
*/
|
|
||||||
void logEvent(UUID deploymentId, UUID contentUuid, String message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Логирование события с автоматической меткой времени
|
|
||||||
*
|
|
||||||
* @param deploymentId ID деплоймента LMS
|
|
||||||
* @param contentUuid UUID контента
|
|
||||||
* @param action Действие (например, "LOGIN", "TASK_LOADED")
|
|
||||||
* @param details Детали события
|
|
||||||
*/
|
|
||||||
void logAction(UUID deploymentId, UUID contentUuid, String action, String details);
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
package ru.oa2.lti.application.service.history;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import ru.oa2.lti.infrastructure.adapter.persistence.jpa.repository.HistoryJpaRepository;
|
|
||||||
import ru.oa2.lti.infrastructure.adapter.persistence.jpa.entity.HistoryEntity;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Service
|
|
||||||
public class HistoryServiceImpl implements HistoryService {
|
|
||||||
|
|
||||||
private final HistoryJpaRepository historyRepository;
|
|
||||||
|
|
||||||
public HistoryServiceImpl(HistoryJpaRepository historyRepository) {
|
|
||||||
this.historyRepository = historyRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
|
||||||
public void logEvent(UUID deploymentId, UUID contentUuid, String message) {
|
|
||||||
try {
|
|
||||||
HistoryEntity history = new HistoryEntity();
|
|
||||||
history.setDeploymentId(deploymentId);
|
|
||||||
history.setContentUuid(contentUuid);
|
|
||||||
history.setCreated(LocalDateTime.now());
|
|
||||||
history.setMessage(truncateMessage(message));
|
|
||||||
|
|
||||||
historyRepository.save(history);
|
|
||||||
log.debug("Event logged: deploymentId={}, contentUuid={}, message={}",
|
|
||||||
deploymentId, contentUuid, message);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to log event: deploymentId={}, contentUuid={}, message={}",
|
|
||||||
deploymentId, contentUuid, message, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
|
||||||
public void logAction(UUID deploymentId, UUID contentUuid, String action, String details) {
|
|
||||||
String message = String.format("[%s] %s", action, details);
|
|
||||||
logEvent(deploymentId, contentUuid, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Обрезка сообщения до максимальной длины (2048 символов)
|
|
||||||
*/
|
|
||||||
private String truncateMessage(String message) {
|
|
||||||
if (message == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (message.length() > 2048) {
|
|
||||||
return message.substring(0, 2045) + "...";
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package ru.oa2.lti.application.service.jwt;
|
package ru.oa2.lti.application.service.jwt;
|
||||||
|
|
||||||
import ru.oa2.lti.domain.model.auth.IdTokenPayload;
|
import ru.oa2.lti.application.dto.auth.IdTokenPayload;
|
||||||
import ru.oa2.lti.domain.model.auth.Payload;
|
import ru.oa2.lti.application.dto.auth.Payload;
|
||||||
|
|
||||||
public interface JwtService {
|
public interface JwtService {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package ru.oa2.lti.application.service.jwt;
|
||||||
import com.nimbusds.jose.shaded.gson.internal.LinkedTreeMap;
|
import com.nimbusds.jose.shaded.gson.internal.LinkedTreeMap;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import ru.oa2.lti.domain.model.auth.*;
|
import ru.oa2.lti.application.dto.auth.*;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
import ru.oa2.lti.application.service.jwt.JwtService;
|
import ru.oa2.lti.application.service.jwt.JwtService;
|
||||||
import ru.oa2.lti.domain.model.results.GradingType;
|
import ru.oa2.lti.application.dto.result.GradingType;
|
||||||
import ru.oa2.lti.domain.model.results.ProgressType;
|
import ru.oa2.lti.application.dto.result.ProgressType;
|
||||||
import ru.oa2.lti.domain.model.results.ResultRequest;
|
import ru.oa2.lti.application.dto.result.ResultRequest;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package ru.oa2.lti.application.service.task;
|
package ru.oa2.lti.application.service.task;
|
||||||
|
|
||||||
import ru.oa2.lti.domain.model.ResultRequest;
|
import ru.oa2.lti.application.dto.result.ResultResponse;
|
||||||
import ru.oa2.lti.domain.model.results.ResultResponse;
|
import ru.oa2.lti.application.dto.task.CheckTaskRequest;
|
||||||
import ru.oa2.lti.domain.model.task.RequestUpdateTask;
|
import ru.oa2.lti.application.dto.task.RequestUpdateTask;
|
||||||
import ru.oa2.lti.domain.model.task.TaskData;
|
import ru.oa2.lti.application.dto.task.TaskData;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ public interface TaskService {
|
||||||
/*
|
/*
|
||||||
Запуск проверки через скрипт
|
Запуск проверки через скрипт
|
||||||
*/
|
*/
|
||||||
ResultResponse checkTask(ResultRequest resultRequest);
|
ResultResponse checkTask(CheckTaskRequest request);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Сохранение/обновление task-а
|
Сохранение/обновление task-а
|
||||||
|
|
|
||||||
|
|
@ -4,31 +4,29 @@ import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import ru.oa2.lti.application.infrastructure.lms.LMSService;
|
|
||||||
import ru.oa2.lti.application.infrastructure.runner.Runner;
|
|
||||||
import ru.oa2.lti.application.service.history.HistoryService;
|
|
||||||
import ru.oa2.lti.application.service.results.ResultService;
|
import ru.oa2.lti.application.service.results.ResultService;
|
||||||
import ru.oa2.lti.domain.exception.TaskNotFoundException;
|
import ru.oa2.lti.domain.exception.TaskNotFoundException;
|
||||||
import ru.oa2.lti.domain.model.Task;
|
import ru.oa2.lti.domain.model.Task;
|
||||||
import ru.oa2.lti.domain.repository.TaskRepository;
|
import ru.oa2.lti.domain.port.out.HistoryRepository;
|
||||||
|
import ru.oa2.lti.domain.port.out.LMSGateway;
|
||||||
|
import ru.oa2.lti.domain.port.out.ScriptExecutor;
|
||||||
|
import ru.oa2.lti.domain.port.out.TaskRepository;
|
||||||
import ru.oa2.lti.domain.valueobject.*;
|
import ru.oa2.lti.domain.valueobject.*;
|
||||||
import ru.oa2.lti.domain.model.ResultRequest;
|
import ru.oa2.lti.application.dto.result.ResultResponse;
|
||||||
import ru.oa2.lti.domain.model.results.ResultResponse;
|
import ru.oa2.lti.application.dto.task.CheckTaskRequest;
|
||||||
import ru.oa2.lti.domain.model.task.RequestUpdateTask;
|
import ru.oa2.lti.application.dto.task.RequestUpdateTask;
|
||||||
import ru.oa2.lti.domain.model.task.TaskData;
|
import ru.oa2.lti.application.dto.task.TaskData;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Реализация TaskService, следующая принципам Clean Architecture и DDD.
|
* Реализация TaskService, следующая принципам Clean Architecture и DDD.
|
||||||
*
|
*
|
||||||
* Улучшения по сравнению со старой версией:
|
* Улучшения:
|
||||||
* - Использует доменную модель Task вместо JPA Entity
|
* - Использует доменную модель Task вместо JPA Entity
|
||||||
* - Зависит от порта TaskRepository (domain), а не от конкретной реализации
|
* - Зависит от портов (domain), а не от конкретных реализаций
|
||||||
* - Бизнес-логика теперь в доменной модели Task
|
* - Бизнес-логика в доменной модели Task
|
||||||
* - Сервис только координирует взаимодействие между компонентами
|
* - Сервис только координирует взаимодействие между компонентами
|
||||||
* - Явная обработка исключений
|
|
||||||
* - Транзакции только на методах записи
|
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
|
|
@ -36,27 +34,24 @@ import java.util.UUID;
|
||||||
public class TaskServiceImpl implements TaskService {
|
public class TaskServiceImpl implements TaskService {
|
||||||
|
|
||||||
private final TaskRepository taskRepository;
|
private final TaskRepository taskRepository;
|
||||||
private final Runner runner;
|
private final ScriptExecutor scriptExecutor;
|
||||||
private final HistoryService historyService;
|
private final HistoryRepository historyRepository;
|
||||||
private final ResultService resultService;
|
private final ResultService resultService;
|
||||||
private final LMSService lmsService;
|
private final LMSGateway lmsGateway;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public TaskData getTask(UUID contextId) {
|
public TaskData getTask(UUID contextId) {
|
||||||
log.info("Getting task for context: {}", contextId);
|
log.info("Getting task for context: {}", contextId);
|
||||||
|
|
||||||
// Используем доменный репозиторий через порт
|
|
||||||
Task task = taskRepository.findByContextId(contextId)
|
Task task = taskRepository.findByContextId(contextId)
|
||||||
.orElseThrow(() -> new TaskNotFoundException(contextId));
|
.orElseThrow(() -> new TaskNotFoundException(contextId));
|
||||||
|
|
||||||
// Преобразуем в DTO для presentation layer
|
|
||||||
TaskData data = taskToDto(task);
|
TaskData data = taskToDto(task);
|
||||||
|
|
||||||
// Выполняем инициализационный скрипт
|
|
||||||
Script initScript = task.getInitScript();
|
Script initScript = task.getInitScript();
|
||||||
if (initScript.isExecutable()) {
|
if (initScript.isExecutable()) {
|
||||||
boolean success = runner.run(contextId, initScript.content());
|
boolean success = scriptExecutor.runInitScript(contextId, initScript.content());
|
||||||
if (!success) {
|
if (!success) {
|
||||||
log.error("Init script failed for context: {}", contextId);
|
log.error("Init script failed for context: {}", contextId);
|
||||||
throw new RuntimeException("Initialization script execution failed");
|
throw new RuntimeException("Initialization script execution failed");
|
||||||
|
|
@ -68,16 +63,12 @@ public class TaskServiceImpl implements TaskService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public ResultResponse checkTask(ResultRequest resultRequest) {
|
public ResultResponse checkTask(CheckTaskRequest request) {
|
||||||
log.info("Checking task for clientId: {}", resultRequest.clientId());
|
log.info("Checking task for clientId: {}", request.clientId());
|
||||||
|
|
||||||
// Получаем access token от LMS
|
String accessToken = lmsGateway.exchangeForAccessToken(request.clientId());
|
||||||
String accessToken = lmsService.exchangeForAccessToken(resultRequest.clientId());
|
resultService.setResult(accessToken, request.idToken());
|
||||||
|
|
||||||
// Отправляем результат
|
|
||||||
resultService.setResult(accessToken, resultRequest.idToken());
|
|
||||||
|
|
||||||
// TODO: реализовать запуск скрипта проверки
|
|
||||||
return new ResultResponse("success");
|
return new ResultResponse("success");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,21 +78,16 @@ public class TaskServiceImpl implements TaskService {
|
||||||
log.info("Saving task for context: {}", requestUpdateTask.getContextId());
|
log.info("Saving task for context: {}", requestUpdateTask.getContextId());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Загружаем существующую задачу
|
|
||||||
Task task = taskRepository.findByContextId(requestUpdateTask.getContextId())
|
Task task = taskRepository.findByContextId(requestUpdateTask.getContextId())
|
||||||
.orElseThrow(() -> new TaskNotFoundException(requestUpdateTask.getContextId()));
|
.orElseThrow(() -> new TaskNotFoundException(requestUpdateTask.getContextId()));
|
||||||
|
|
||||||
String oldName = task.getName().value();
|
String oldName = task.getName().value();
|
||||||
|
|
||||||
// Обновляем задачу через доменную модель (бизнес-логика внутри)
|
|
||||||
updateTaskFromDto(task, requestUpdateTask.getData());
|
updateTaskFromDto(task, requestUpdateTask.getData());
|
||||||
|
|
||||||
// Сохраняем через порт репозитория
|
|
||||||
taskRepository.save(task);
|
taskRepository.save(task);
|
||||||
|
|
||||||
// Логируем действие
|
historyRepository.logAction(
|
||||||
historyService.logAction(
|
null,
|
||||||
null, // deploymentId нужно получать из контекста
|
|
||||||
requestUpdateTask.getContextId(),
|
requestUpdateTask.getContextId(),
|
||||||
"TASK_SAVED",
|
"TASK_SAVED",
|
||||||
String.format("Task saved: %s (was: %s)",
|
String.format("Task saved: %s (was: %s)",
|
||||||
|
|
@ -111,7 +97,7 @@ public class TaskServiceImpl implements TaskService {
|
||||||
return new ResultResponse("success");
|
return new ResultResponse("success");
|
||||||
} catch (TaskNotFoundException e) {
|
} catch (TaskNotFoundException e) {
|
||||||
log.error("Task not found for context: {}", requestUpdateTask.getContextId(), e);
|
log.error("Task not found for context: {}", requestUpdateTask.getContextId(), e);
|
||||||
historyService.logAction(
|
historyRepository.logAction(
|
||||||
null,
|
null,
|
||||||
requestUpdateTask.getContextId(),
|
requestUpdateTask.getContextId(),
|
||||||
"TASK_SAVE_ERROR",
|
"TASK_SAVE_ERROR",
|
||||||
|
|
@ -120,7 +106,7 @@ public class TaskServiceImpl implements TaskService {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error saving task", e);
|
log.error("Error saving task", e);
|
||||||
historyService.logAction(
|
historyRepository.logAction(
|
||||||
null,
|
null,
|
||||||
requestUpdateTask.getContextId(),
|
requestUpdateTask.getContextId(),
|
||||||
"TASK_SAVE_ERROR",
|
"TASK_SAVE_ERROR",
|
||||||
|
|
@ -130,9 +116,6 @@ public class TaskServiceImpl implements TaskService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Преобразует доменную модель Task в DTO TaskData
|
|
||||||
*/
|
|
||||||
private TaskData taskToDto(Task task) {
|
private TaskData taskToDto(Task task) {
|
||||||
return new TaskData(
|
return new TaskData(
|
||||||
task.getName().value(),
|
task.getName().value(),
|
||||||
|
|
@ -143,17 +126,12 @@ public class TaskServiceImpl implements TaskService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Обновляет доменную модель Task из DTO TaskData
|
|
||||||
*/
|
|
||||||
private void updateTaskFromDto(Task task, TaskData data) {
|
private void updateTaskFromDto(Task task, TaskData data) {
|
||||||
// Используем Value Objects с валидацией
|
|
||||||
task.update(
|
task.update(
|
||||||
TaskName.of(data.getName()),
|
TaskName.of(data.getName()),
|
||||||
Description.of(data.getDescription())
|
Description.of(data.getDescription())
|
||||||
);
|
);
|
||||||
|
|
||||||
// Обновляем скрипты
|
|
||||||
if (data.getInitScript() != null) {
|
if (data.getInitScript() != null) {
|
||||||
task.setScript(Script.of(ScriptType.INITIALIZATION, data.getInitScript()));
|
task.setScript(Script.of(ScriptType.INITIALIZATION, data.getInitScript()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package ru.oa2.lti.application.usecase.lti;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.oa2.lti.application.dto.auth.IdTokenPayload;
|
||||||
|
import ru.oa2.lti.application.service.jwt.JwtService;
|
||||||
|
import ru.oa2.lti.domain.port.out.HistoryRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use Case для обработки редиректа после LTI аутентификации.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class HandleLtiRedirectUseCase {
|
||||||
|
|
||||||
|
private final JwtService jwtService;
|
||||||
|
private final HistoryRepository historyRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Результат обработки редиректа
|
||||||
|
*/
|
||||||
|
public record LtiRedirectResult(
|
||||||
|
IdTokenPayload idTokenPayload,
|
||||||
|
String redirectUrl
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обрабатывает редирект после OAuth аутентификации
|
||||||
|
*/
|
||||||
|
public LtiRedirectResult execute(String idToken, String state) {
|
||||||
|
log.info("Handling LTI redirect");
|
||||||
|
|
||||||
|
// Декодируем id_token
|
||||||
|
var idTokenPayload = jwtService.getTokenPayload(idToken);
|
||||||
|
|
||||||
|
// Логируем перенаправление
|
||||||
|
historyRepository.logAction(
|
||||||
|
idTokenPayload.getDeploymentId(),
|
||||||
|
idTokenPayload.getContext() != null ? idTokenPayload.getContext().id() : null,
|
||||||
|
"LTI_REDIRECT",
|
||||||
|
"User redirected after OAuth authentication"
|
||||||
|
);
|
||||||
|
|
||||||
|
return new LtiRedirectResult(idTokenPayload, "/tool/lti/task");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package ru.oa2.lti.application.usecase.lti;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.oa2.lti.application.dto.auth.LtiLogin;
|
||||||
|
import ru.oa2.lti.application.dto.auth.Payload;
|
||||||
|
import ru.oa2.lti.application.service.jwt.JwtService;
|
||||||
|
import ru.oa2.lti.domain.port.out.HistoryRepository;
|
||||||
|
import ru.oa2.lti.domain.port.out.LMSGateway;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use Case для обработки LTI логина.
|
||||||
|
* Инкапсулирует бизнес-логику аутентификации через LTI 1.3.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ProcessLtiLoginUseCase {
|
||||||
|
|
||||||
|
private final JwtService jwtService;
|
||||||
|
private final LMSGateway lmsGateway;
|
||||||
|
private final HistoryRepository historyRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Результат обработки LTI логина
|
||||||
|
*/
|
||||||
|
public record LtiLoginResult(
|
||||||
|
LtiLogin ltiLogin,
|
||||||
|
Payload payload,
|
||||||
|
String redirectResponse,
|
||||||
|
boolean requiresSelection
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Команда для обработки LTI логина
|
||||||
|
*/
|
||||||
|
public record LtiLoginCommand(
|
||||||
|
String clientId,
|
||||||
|
String iss,
|
||||||
|
String loginHint,
|
||||||
|
String ltiDeploymentId,
|
||||||
|
String ltiMessageHint,
|
||||||
|
String targetLinkUri
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обрабатывает LTI логин и возвращает результат
|
||||||
|
*/
|
||||||
|
public LtiLoginResult execute(LtiLoginCommand command) {
|
||||||
|
log.info("Processing LTI login for clientId: {}", command.clientId());
|
||||||
|
|
||||||
|
// Создаём объект LtiLogin
|
||||||
|
var ltiLogin = LtiLogin.builder()
|
||||||
|
.clientId(command.clientId())
|
||||||
|
.iss(command.iss())
|
||||||
|
.loginHint(command.loginHint())
|
||||||
|
.ltiDeploymentId(command.ltiDeploymentId())
|
||||||
|
.ltiMessageHint(command.ltiMessageHint())
|
||||||
|
.targetLinkUri(command.targetLinkUri())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Получаем payload из JWT
|
||||||
|
var payload = jwtService.getPayload(ltiLogin.getLoginHint());
|
||||||
|
|
||||||
|
// Логируем событие входа
|
||||||
|
historyRepository.logAction(
|
||||||
|
payload.getDeploymentId(),
|
||||||
|
payload.getContextId(),
|
||||||
|
"LTI_LOGIN",
|
||||||
|
String.format("User login via LTI 1.3, clientId=%s", command.clientId())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Проверяем, есть ли contextId
|
||||||
|
if (payload.getContextId() != null) {
|
||||||
|
// Выполняем аутентификацию в LMS
|
||||||
|
String redirectResponse = lmsGateway.ltiAuth(
|
||||||
|
command.ltiMessageHint(),
|
||||||
|
command.iss(),
|
||||||
|
command.loginHint()
|
||||||
|
);
|
||||||
|
return new LtiLoginResult(ltiLogin, payload, redirectResponse, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Требуется выбор задания
|
||||||
|
return new LtiLoginResult(ltiLogin, payload, null, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package ru.oa2.lti.domain.model;
|
|
||||||
|
|
||||||
//TODO 2 одинаковых имени? )
|
|
||||||
public record ResultRequest(
|
|
||||||
String clientId,
|
|
||||||
String idToken
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package ru.oa2.lti.domain.port.out;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выходной порт для записи истории событий.
|
||||||
|
* Определяет контракт для логирования действий в системе.
|
||||||
|
*
|
||||||
|
* Принципы:
|
||||||
|
* - Hexagonal Architecture: это выходной порт (driven port)
|
||||||
|
* - Domain слой определяет интерфейс, Infrastructure реализует
|
||||||
|
*/
|
||||||
|
public interface HistoryRepository {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Записывает событие в историю
|
||||||
|
* @param deploymentId идентификатор развертывания
|
||||||
|
* @param contentUuid UUID контента
|
||||||
|
* @param message сообщение события
|
||||||
|
*/
|
||||||
|
void logEvent(UUID deploymentId, UUID contentUuid, String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Записывает действие с типом в историю
|
||||||
|
* @param deploymentId идентификатор развертывания
|
||||||
|
* @param contentUuid UUID контента
|
||||||
|
* @param actionType тип действия
|
||||||
|
* @param details детали действия
|
||||||
|
*/
|
||||||
|
void logAction(UUID deploymentId, UUID contentUuid, String actionType, String details);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package ru.oa2.lti.domain.port.out;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выходной порт для интеграции с LMS (Learning Management System).
|
||||||
|
* Определяет контракт для взаимодействия с внешней LMS системой.
|
||||||
|
*
|
||||||
|
* Принципы:
|
||||||
|
* - Hexagonal Architecture: это выходной порт (driven port)
|
||||||
|
* - Domain слой определяет интерфейс, Infrastructure реализует
|
||||||
|
*/
|
||||||
|
public interface LMSGateway {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет LTI аутентификацию
|
||||||
|
* @param ltiLoginHint подсказка для входа LTI
|
||||||
|
* @param iss издатель токена
|
||||||
|
* @param loginHint подсказка для входа
|
||||||
|
* @return URL для редиректа или HTML ответ
|
||||||
|
*/
|
||||||
|
String ltiAuth(String ltiLoginHint, String iss, String loginHint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обменивает учетные данные на access token для работы с LMS API
|
||||||
|
* @param clientId идентификатор клиента
|
||||||
|
* @return access token
|
||||||
|
*/
|
||||||
|
String exchangeForAccessToken(String clientId);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package ru.oa2.lti.domain.port.out;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выходной порт для выполнения скриптов.
|
||||||
|
* Определяет контракт для запуска, проверки и удаления скриптов.
|
||||||
|
*
|
||||||
|
* Принципы:
|
||||||
|
* - Hexagonal Architecture: это выходной порт (driven port)
|
||||||
|
* - Domain слой определяет интерфейс, Infrastructure реализует
|
||||||
|
*/
|
||||||
|
public interface ScriptExecutor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запускает инициализационный скрипт для пользователя
|
||||||
|
* @param contextId идентификатор контекста
|
||||||
|
* @param script содержимое скрипта
|
||||||
|
* @return true если скрипт выполнен успешно
|
||||||
|
*/
|
||||||
|
boolean runInitScript(UUID contextId, String script);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запускает скрипт проверки для пользователя
|
||||||
|
* @param contextId идентификатор контекста
|
||||||
|
* @param script содержимое скрипта
|
||||||
|
* @return true если проверка прошла успешно
|
||||||
|
*/
|
||||||
|
boolean runVerificationScript(UUID contextId, String script);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запускает скрипт удаления/очистки для пользователя
|
||||||
|
* @param contextId идентификатор контекста
|
||||||
|
* @param script содержимое скрипта
|
||||||
|
* @return true если очистка прошла успешно
|
||||||
|
*/
|
||||||
|
boolean runCleanupScript(UUID contextId, String script);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.domain.repository;
|
package ru.oa2.lti.domain.port.out;
|
||||||
|
|
||||||
import ru.oa2.lti.domain.model.Task;
|
import ru.oa2.lti.domain.model.Task;
|
||||||
import ru.oa2.lti.domain.valueobject.TaskId;
|
import ru.oa2.lti.domain.valueobject.TaskId;
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package ru.oa2.lti.infrastructure.adapter.history;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import ru.oa2.lti.domain.port.out.HistoryRepository;
|
||||||
|
import ru.oa2.lti.infrastructure.adapter.persistence.jpa.entity.HistoryEntity;
|
||||||
|
import ru.oa2.lti.infrastructure.adapter.persistence.jpa.repository.HistoryJpaRepository;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPA адаптер для записи истории событий.
|
||||||
|
* Реализует выходной порт HistoryRepository.
|
||||||
|
*
|
||||||
|
* Принципы:
|
||||||
|
* - Hexagonal Architecture: это исходящий адаптер (driven adapter)
|
||||||
|
* - Инкапсулирует детали работы с JPA
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class HistoryJpaAdapter implements HistoryRepository {
|
||||||
|
|
||||||
|
private static final int MAX_MESSAGE_LENGTH = 2048;
|
||||||
|
private final HistoryJpaRepository jpaRepository;
|
||||||
|
|
||||||
|
public HistoryJpaAdapter(HistoryJpaRepository jpaRepository) {
|
||||||
|
this.jpaRepository = jpaRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
|
public void logEvent(UUID deploymentId, UUID contentUuid, String message) {
|
||||||
|
try {
|
||||||
|
HistoryEntity history = new HistoryEntity();
|
||||||
|
history.setDeploymentId(deploymentId);
|
||||||
|
history.setContentUuid(contentUuid);
|
||||||
|
history.setCreated(LocalDateTime.now());
|
||||||
|
history.setMessage(truncateMessage(message));
|
||||||
|
|
||||||
|
jpaRepository.save(history);
|
||||||
|
log.debug("Event logged: deploymentId={}, contentUuid={}, message={}",
|
||||||
|
deploymentId, contentUuid, message);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to log event: deploymentId={}, contentUuid={}, message={}",
|
||||||
|
deploymentId, contentUuid, message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
|
public void logAction(UUID deploymentId, UUID contentUuid, String actionType, String details) {
|
||||||
|
try {
|
||||||
|
HistoryEntity history = new HistoryEntity();
|
||||||
|
history.setDeploymentId(deploymentId);
|
||||||
|
history.setContentUuid(contentUuid);
|
||||||
|
history.setCreated(LocalDateTime.now());
|
||||||
|
history.setActionType(actionType);
|
||||||
|
history.setMessage(truncateMessage(details));
|
||||||
|
|
||||||
|
jpaRepository.save(history);
|
||||||
|
log.debug("Action logged: deploymentId={}, contentUuid={}, action={}, details={}",
|
||||||
|
deploymentId, contentUuid, actionType, details);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to log action: deploymentId={}, contentUuid={}, action={}",
|
||||||
|
deploymentId, contentUuid, actionType, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String truncateMessage(String message) {
|
||||||
|
if (message == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (message.length() > MAX_MESSAGE_LENGTH) {
|
||||||
|
return message.substring(0, MAX_MESSAGE_LENGTH - 3) + "...";
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.oa2.lti.application.infrastructure.lms;
|
package ru.oa2.lti.infrastructure.adapter.lms;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
|
@ -8,19 +8,27 @@ import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
import ru.oa2.lti.application.config.AppProperties;
|
import ru.oa2.lti.application.config.AppProperties;
|
||||||
import ru.oa2.lti.application.service.jwt.JwtAssertionGenerator;
|
import ru.oa2.lti.application.service.jwt.JwtAssertionGenerator;
|
||||||
import ru.oa2.lti.domain.model.auth.Scope;
|
import ru.oa2.lti.application.dto.auth.Scope;
|
||||||
|
import ru.oa2.lti.domain.port.out.LMSGateway;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Адаптер для интеграции с LMS через REST API.
|
||||||
|
* Реализует выходной порт LMSGateway.
|
||||||
|
*
|
||||||
|
* Принципы:
|
||||||
|
* - Hexagonal Architecture: это исходящий адаптер (driven adapter)
|
||||||
|
* - Инкапсулирует детали HTTP взаимодействия с LMS
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class LMSServiceImpl implements LMSService {
|
public class LMSRestAdapter implements LMSGateway {
|
||||||
|
|
||||||
private final RestClient restClient;
|
private final RestClient restClient;
|
||||||
private final AppProperties appProperties;
|
private final AppProperties appProperties;
|
||||||
|
|
||||||
public LMSServiceImpl(RestClient restClient, AppProperties appProperties) {
|
public LMSRestAdapter(RestClient restClient, AppProperties appProperties) {
|
||||||
this.restClient = restClient;
|
this.restClient = restClient;
|
||||||
this.appProperties = appProperties;
|
this.appProperties = appProperties;
|
||||||
}
|
}
|
||||||
|
|
@ -30,8 +38,8 @@ public class LMSServiceImpl implements LMSService {
|
||||||
var result = restClient
|
var result = restClient
|
||||||
.get()
|
.get()
|
||||||
.uri("/lti/auth?" +
|
.uri("/lti/auth?" +
|
||||||
"state=start" + //TODO а какие могут быть статусы? нужно в Enum перенести
|
"state=start" +
|
||||||
"&code=code1" + //TODO что за коды?
|
"&code=code1" +
|
||||||
"&iss=" + iss +
|
"&iss=" + iss +
|
||||||
"&login_hint=" + loginHint +
|
"&login_hint=" + loginHint +
|
||||||
"&redirect_uri=" + appProperties.lmsUrl() + "/tool/lti/redirect" +
|
"&redirect_uri=" + appProperties.lmsUrl() + "/tool/lti/redirect" +
|
||||||
|
|
@ -47,11 +55,9 @@ public class LMSServiceImpl implements LMSService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String exchangeForAccessToken(String clientId) {
|
public String exchangeForAccessToken(String clientId) {
|
||||||
|
|
||||||
var endpointUrl = appProperties.lmsUrl() + "/lti/token";
|
var endpointUrl = appProperties.lmsUrl() + "/lti/token";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
var clientAssertion = getClientAssertion(clientId, endpointUrl);
|
var clientAssertion = getClientAssertion(clientId, endpointUrl);
|
||||||
var body = getBody(clientAssertion);
|
var body = getBody(clientAssertion);
|
||||||
|
|
||||||
|
|
@ -66,34 +72,31 @@ public class LMSServiceImpl implements LMSService {
|
||||||
if (responseBody != null && responseBody.containsKey("access_token")) {
|
if (responseBody != null && responseBody.containsKey("access_token")) {
|
||||||
return (String) responseBody.get("access_token");
|
return (String) responseBody.get("access_token");
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Ответ не содержит access_token");
|
throw new RuntimeException("Response does not contain access_token");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Ошибка получения токена: " + e.getMessage(), e);
|
throw new RuntimeException("Error getting token: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Не удалось получить access token", e);
|
throw new RuntimeException("Failed to get access token", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getClientAssertion(String clientId, String endpointUrl) throws Exception {
|
private String getClientAssertion(String clientId, String endpointUrl) throws Exception {
|
||||||
// Генерируем client_assertion
|
|
||||||
return JwtAssertionGenerator.generateClientAssertion(
|
return JwtAssertionGenerator.generateClientAssertion(
|
||||||
clientId,
|
clientId,
|
||||||
endpointUrl,
|
endpointUrl,
|
||||||
"my-key-id-1" //TODO должен совпадать с тем, что в JWKS
|
"my-key-id-1"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MultiValueMap<String, String> getBody(String clientAssertion) {
|
private MultiValueMap<String, String> getBody(String clientAssertion) {
|
||||||
|
|
||||||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||||
body.add("grant_type", "client_credentials");
|
body.add("grant_type", "client_credentials");
|
||||||
body.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
|
body.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
|
||||||
body.add("client_assertion", clientAssertion);
|
body.add("client_assertion", clientAssertion);
|
||||||
body.add("scope", Scope.scope(Scope.LINE_ITEM, Scope.SCOPE, Scope.ENDPOINT));
|
body.add("scope", Scope.scope(Scope.LINE_ITEM, Scope.SCOPE, Scope.ENDPOINT));
|
||||||
|
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ package ru.oa2.lti.infrastructure.adapter.persistence.jpa;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import ru.oa2.lti.domain.model.Task;
|
import ru.oa2.lti.domain.model.Task;
|
||||||
import ru.oa2.lti.domain.repository.TaskRepository;
|
import ru.oa2.lti.domain.port.out.TaskRepository;
|
||||||
import ru.oa2.lti.domain.valueobject.TaskId;
|
import ru.oa2.lti.domain.valueobject.TaskId;
|
||||||
import ru.oa2.lti.infrastructure.adapter.persistence.jpa.entity.TaskEntity;
|
import ru.oa2.lti.infrastructure.adapter.persistence.jpa.entity.TaskEntity;
|
||||||
import ru.oa2.lti.infrastructure.adapter.persistence.jpa.mapper.TaskEntityMapper;
|
import ru.oa2.lti.infrastructure.adapter.persistence.jpa.mapper.TaskEntityMapper;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
package ru.oa2.lti.infrastructure.adapter.script;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.oa2.lti.domain.port.out.HistoryRepository;
|
||||||
|
import ru.oa2.lti.domain.port.out.ScriptExecutor;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Адаптер для выполнения Bash скриптов.
|
||||||
|
* Реализует выходной порт ScriptExecutor.
|
||||||
|
*
|
||||||
|
* Принципы:
|
||||||
|
* - Hexagonal Architecture: это исходящий адаптер (driven adapter)
|
||||||
|
* - Инкапсулирует детали выполнения скриптов через bash
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class BashScriptAdapter implements ScriptExecutor {
|
||||||
|
|
||||||
|
private static final String TEMP_DIR = "./temp/";
|
||||||
|
private final HistoryRepository historyRepository;
|
||||||
|
|
||||||
|
public BashScriptAdapter(HistoryRepository historyRepository) {
|
||||||
|
this.historyRepository = historyRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean runInitScript(UUID contextId, String script) {
|
||||||
|
try {
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_RUN_START", "Init script execution started");
|
||||||
|
boolean result = runScript(contextId, script);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_RUN_SUCCESS", "Init script completed successfully");
|
||||||
|
} else {
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_RUN_FAILED", "Init script failed with non-zero exit code");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("Init script error: {}", ex.getMessage());
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_RUN_ERROR", "Init script error: " + ex.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean runVerificationScript(UUID contextId, String script) {
|
||||||
|
try {
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_CHECK_START", "Verification script execution started");
|
||||||
|
boolean result = runScript(contextId, script);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_CHECK_SUCCESS", "Verification script completed successfully");
|
||||||
|
} else {
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_CHECK_FAILED", "Verification script failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("Verification script error: {}", ex.getMessage());
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_CHECK_ERROR", "Verification script error: " + ex.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean runCleanupScript(UUID contextId, String script) {
|
||||||
|
try {
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_DELETE_START", "Cleanup script execution started");
|
||||||
|
boolean result = runScript(contextId, script);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_DELETE_SUCCESS", "Cleanup script completed successfully");
|
||||||
|
} else {
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_DELETE_FAILED", "Cleanup script failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("Cleanup script error: {}", ex.getMessage());
|
||||||
|
historyRepository.logAction(null, contextId, "SCRIPT_DELETE_ERROR", "Cleanup script error: " + ex.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean runScript(UUID contextId, String script) throws Exception {
|
||||||
|
var processBuilder = new ProcessBuilder();
|
||||||
|
processBuilder.command("bash", saveFile(contextId, 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("Script output: {}", output);
|
||||||
|
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
return exitCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String saveFile(UUID contextId, String script) throws IOException {
|
||||||
|
Path path = Paths.get(TEMP_DIR + contextId.toString());
|
||||||
|
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
Files.createDirectories(path);
|
||||||
|
}
|
||||||
|
var tm = System.currentTimeMillis();
|
||||||
|
var scriptPath = Files.write(
|
||||||
|
Paths.get(TEMP_DIR + contextId + "/" + tm),
|
||||||
|
script.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
return scriptPath.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue