рефакторинг кода

This commit is contained in:
Anton Dzyk 2025-12-24 13:39:39 +03:00
parent 23ea2ed09f
commit 6f42fe0ee6
57 changed files with 395 additions and 448 deletions

View File

@ -1,68 +0,0 @@
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;
import ru.oa2.lti.repository.entities.LMSContent;
import ru.oa2.lti.repository.entities.Task;
import ru.oa2.lti.service.jwt.JwtService;
import ru.oa2.lti.service.jwt.Payload;
import ru.oa2.lti.service.results.dto.ResultResponse;
import java.util.Collection;
import java.util.UUID;
@Service
@Transactional
public class ApplicationService {
private final JwtService jwtService;
private final LMSContentRepository lmsContentRepository;
private final Runner runner;
public ApplicationService(JwtService jwtService,
LMSContentRepository lmsContentRepository,
Runner runner) {
this.jwtService = jwtService;
this.lmsContentRepository = lmsContentRepository;
this.runner = runner;
}
public Payload getPayload(LtiLogin ltiLogin) {
return jwtService.getPayload(ltiLogin.getLoginHint());
}
public String getTask(UUID contextId) {
var content = lmsContentRepository.getLMSContentByContentId(contextId);
if(content.isPresent()) {
LMSContent lmsContent = content.get();
Collection<Task> tasks = lmsContent.getTasks();
//TODO добавить версию в Task и выбирать самую старшую и опубликованную
TaskData data = tasks.stream().findFirst().get().getData();
if (!runner.run(contextId, data.getInitScript())) {
return "Init script FAILED";
}
return data.getDescription();
} else {
return "Not Page";
}
}
public ResultResponse updateTask(String body) {
return new ResultResponse("success");
}
public ResultResponse saveResult(String body) {
//TODO заполнять из результатов проверок check скрипта
return new ResultResponse("success");
}
}

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.config;
package ru.oa2.lti.application.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.config;
package ru.oa2.lti.application.config;
import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@ -1,30 +1,30 @@
package ru.oa2.lti.controller;
package ru.oa2.lti.application.controller;
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 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.application.infrastructure.lms.LMSService;
import ru.oa2.lti.application.service.jwt.JwtService;
import ru.oa2.lti.domain.model.auth.LtiLogin;
import java.util.Objects;
@Slf4j
/*
Входная точка в external tool по протоколу LTI 1.3
#TODO расписать процесс и добавить линк на доку
*/
@Controller
@RequestMapping("/tool/lti")
public class LaunchController {
private final ApplicationService service;
private final JwtService jwtService;
private final LMSService lmsService;
public LaunchController(ApplicationService applicationService,
public LaunchController(JwtService jwtService,
LMSService lmsService) {
this.service = applicationService;
this.jwtService = jwtService;
this.lmsService = lmsService;
}
@ -47,9 +47,7 @@ public class LaunchController {
.targetLinkUri(targetLinkUri)
.build();
log.info("BODY: {}", ltiLogin);
var payload = service.getPayload(ltiLogin);
var payload = jwtService.getPayload(ltiLogin.getLoginHint());
// 4. Сохранить access token в сессии
HttpSession session = request.getSession();
@ -66,37 +64,9 @@ public class LaunchController {
return "redirect:/tool/lti/select";
}
@PostMapping("/task")
public ResponseEntity updateTask() {
return service.updateTask("TODO");
}
@GetMapping("/task")
public String showDockerTas(Model model,
HttpServletRequest request) {
var session = request.getSession();
var payload = (Payload) session.getAttribute("payload");
if (payload != null) {
var data = service.getTask(payload.getContextId());
if (data == null) {
return "redirect:/error";
}
model.addAttribute("description", data);
}
if (Objects.requireNonNull(payload).getCoach()) {
return "task-editor";
}
return "task";
}
@GetMapping("/select")
public String select(Model model) {
//TODO доработать форму выбора существующих заданий
return "select";
}
@ -105,10 +75,6 @@ public class LaunchController {
@RequestParam("state") String state,
HttpServletRequest request) {
if (log.isDebugEnabled()) {
log.debug("/redirect idToken: {}, state: {}", idToken, state);
}
HttpSession session = request.getSession();
session.setAttribute("id_token", idToken);
session.setAttribute("state", state);

View File

@ -0,0 +1,80 @@
package ru.oa2.lti.application.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import ru.oa2.lti.domain.model.auth.Payload;
import ru.oa2.lti.application.service.task.TaskService;
import ru.oa2.lti.domain.model.auth.LtiLogin;
import ru.oa2.lti.domain.model.ResultRequest;
import java.util.Objects;
/*
Контроллер работы с задачами(лабораторными работами)
GET - возвращает контекст лабораторной работы
POST - обновление/создание лабораторной работы (только для преподавателей/администраторов)
POST /submit - отправка на автоматическую проверку
*/
@Controller
@RequestMapping("/tool/lti/task")
public class TaskController {
private final TaskService taskService;
public TaskController(TaskService taskService) {
this.taskService = taskService;
}
@GetMapping
public String showDockerTas(Model model,
HttpServletRequest request) {
var session = request.getSession();
var payload = (Payload) session.getAttribute("payload");
if (payload != null) {
var data = taskService.getTask(payload.getContextId());
if (data == null) {
return "redirect:/error";
}
model.addAttribute("description", data);
}
if (Objects.requireNonNull(payload).getCoach()) {
return "task-editor";
}
return "task";
}
@PostMapping
public ResponseEntity updateTask() {
//TODO exception
taskService.saveTask("TODO", "TODO", "TODO");
return ResponseEntity.accepted().build();
}
@PostMapping("/submit")
public ResponseEntity result(HttpServletRequest req) {
HttpSession session = req.getSession(false);
if (session == null) return ResponseEntity.status(401).build();
var ltiLogin = (LtiLogin) session.getAttribute("lti_login");
var idToken = (String) session.getAttribute("id_token");
return ResponseEntity
.accepted()
.body(taskService.checkTask(new ResultRequest(ltiLogin.getClientId(), idToken)));
}
}

View File

@ -0,0 +1,5 @@
package ru.oa2.lti.application.domain.carecase;
public class GetTask {
}

View File

@ -0,0 +1,9 @@
package ru.oa2.lti.application.infrastructure.lms;
public interface LMSService {
String ltiAuth(String ltiLoginHint, String iss, String loginHint);
// Вызывается после LTI-запуска
String exchangeForAccessToken(String clientId);
}

View File

@ -0,0 +1,99 @@
package ru.oa2.lti.application.infrastructure.lms;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;
import ru.oa2.lti.application.config.AppProperties;
import ru.oa2.lti.application.service.jwt.JwtAssertionGenerator;
import ru.oa2.lti.domain.model.auth.Scope;
import java.util.LinkedHashMap;
import java.util.Map;
@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" + //TODO а какие могут быть статусы? нужно в Enum перенести
"&code=code1" + //TODO что за коды?
"&iss=" + iss +
"&login_hint=" + loginHint +
"&redirect_uri=" + appProperties.lmsUrl() + "/tool/lti/redirect" +
"&lti_message_hint=" + ltiLoginHint)
.retrieve();
var body = result.toEntity(String.class).getBody();
if (log.isDebugEnabled()) {
log.debug("lti/auth RESPONSE: {}", body);
}
return body;
}
@Override
public String exchangeForAccessToken(String clientId) {
var endpointUrl = appProperties.lmsUrl() + "/lti/token";
try {
var clientAssertion = getClientAssertion(clientId, endpointUrl);
var body = getBody(clientAssertion);
try {
var responseBody = restClient.post()
.uri(endpointUrl)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(body)
.retrieve()
.body(Map.class);
if (responseBody != null && responseBody.containsKey("access_token")) {
return (String) responseBody.get("access_token");
} else {
throw new RuntimeException("Ответ не содержит access_token");
}
} catch (Exception e) {
throw new RuntimeException("Ошибка получения токена: " + e.getMessage(), e);
}
} catch (Exception e) {
throw new RuntimeException("Не удалось получить access token", e);
}
}
private String getClientAssertion(String clientId, String endpointUrl) throws Exception {
// Генерируем client_assertion
return JwtAssertionGenerator.generateClientAssertion(
clientId,
endpointUrl,
"my-key-id-1" //TODO должен совпадать с тем, что в JWKS
);
}
private MultiValueMap<String, String> getBody(String clientAssertion) {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "client_credentials");
body.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
body.add("client_assertion", clientAssertion);
body.add("scope", Scope.scope(Scope.LINE_ITEM, Scope.SCOPE, Scope.ENDPOINT));
return body;
}
}

View File

@ -1,8 +1,8 @@
package ru.oa2.lti.repository;
package ru.oa2.lti.application.infrastructure.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.oa2.lti.repository.entities.LMSContent;
import ru.oa2.lti.application.infrastructure.repository.entities.LMSContent;
import java.util.Optional;
import java.util.UUID;

View File

@ -1,8 +1,8 @@
package ru.oa2.lti.repository;
package ru.oa2.lti.application.infrastructure.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.oa2.lti.repository.entities.TaskQueue;
import ru.oa2.lti.application.infrastructure.repository.entities.TaskQueue;
@Repository
public interface TaskQueueRepository extends JpaRepository<TaskQueue, Long> {

View File

@ -1,7 +1,6 @@
package ru.oa2.lti.repository.entities;
package ru.oa2.lti.application.infrastructure.repository.entities;
import jakarta.persistence.*;
import lombok.Data;
import lombok.Getter;
import java.util.Collection;

View File

@ -1,11 +1,11 @@
package ru.oa2.lti.repository.entities;
package ru.oa2.lti.application.infrastructure.repository.entities;
import jakarta.persistence.*;
import lombok.Getter;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import ru.oa2.lti.model.TaskData;
import ru.oa2.lti.model.TaskType;
import ru.oa2.lti.domain.model.task.TaskData;
import ru.oa2.lti.domain.model.task.TaskType;
@Getter
@Entity

View File

@ -1,9 +1,8 @@
package ru.oa2.lti.repository.entities;
package ru.oa2.lti.application.infrastructure.repository.entities;
import jakarta.persistence.*;
import lombok.Data;
import lombok.Getter;
import ru.oa2.lti.model.TaskQueueStatus;
import ru.oa2.lti.domain.model.task.TaskQueueStatus;
@Getter
@Entity

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.infrastructure.runner;
package ru.oa2.lti.application.infrastructure.runner;
import java.util.UUID;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.infrastructure.runner;
package ru.oa2.lti.application.infrastructure.runner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

View File

@ -1,11 +1,11 @@
package ru.oa2.lti.service.jwt;
package ru.oa2.lti.application.service.jwt;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import ru.oa2.lti.utils.Keys;
import ru.oa2.lti.application.utils.Keys;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;

View File

@ -0,0 +1,11 @@
package ru.oa2.lti.application.service.jwt;
import ru.oa2.lti.domain.model.auth.IdTokenPayload;
import ru.oa2.lti.domain.model.auth.Payload;
public interface JwtService {
Payload getPayload(String jwt);
IdTokenPayload getTokenPayload(String jwt);
}

View File

@ -1,8 +1,9 @@
package ru.oa2.lti.service.jwt;
package ru.oa2.lti.application.service.jwt;
import com.nimbusds.jose.shaded.gson.internal.LinkedTreeMap;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.stereotype.Service;
import ru.oa2.lti.domain.model.auth.*;
import java.time.Instant;
import java.time.LocalDateTime;

View File

@ -0,0 +1,6 @@
package ru.oa2.lti.application.service.results;
public interface ResultService {
void setResult(String accessToken, String idToken);
}

View File

@ -1,14 +1,13 @@
package ru.oa2.lti.service.results;
package ru.oa2.lti.application.service.results;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import ru.oa2.lti.service.jwt.IdTokenPayload;
import ru.oa2.lti.service.results.dto.GradingType;
import ru.oa2.lti.service.results.dto.Lineitems;
import ru.oa2.lti.service.results.dto.ProgressType;
import ru.oa2.lti.service.results.dto.ResultRequest;
import ru.oa2.lti.application.service.jwt.JwtService;
import ru.oa2.lti.domain.model.results.GradingType;
import ru.oa2.lti.domain.model.results.ProgressType;
import ru.oa2.lti.domain.model.results.ResultRequest;
import java.time.LocalDateTime;
@ -18,29 +17,26 @@ public class ResultServiceImpl implements ResultService {
private final RestClient restClient;
private final ObjectMapper mapper;
private final JwtService jwtService;
private final String CONTENT_TYPE = "application/vnd.ims.lis.v1.score+json";
private final String ACCEPT = "application/vnd.ims.lis.v1.score+json";
public ResultServiceImpl(RestClient client,
ObjectMapper mapper,
JwtService jwtService) {
public ResultServiceImpl(RestClient client, ObjectMapper mapper) {
this.restClient = client;
this.mapper = mapper;
this.jwtService = jwtService;
}
@Override
public void setResult(String accessToken, IdTokenPayload idToken) {
public void setResult(String accessToken, String idTokenString) {
// http://openolat.local/lti/ags/b0d12061-2564-47a0-9291-b0d226f1eefe/context/112999233990970/lineitems/112999233990979/lineitem
var idToken = jwtService.getTokenPayload(idTokenString);
var resp = restClient.get()
.uri("http://openolat.local/lti/ags/b0d12061-2564-47a0-9291-b0d226f1eefe/context/112999233990970/lineitems/112999233990979/lineitem")
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/vnd.ims.lis.v1.score+json")
.header("Accept", "application/vnd.ims.lis.v1.score+json")
.retrieve();
var respBody = resp.toEntity(String.class).getBody();
try {
Lineitems lineitems = mapper.readValue(respBody, Lineitems.class);
var body = mapper.writeValueAsString(
ResultRequest.builder()
.scoreGiven(1L)
@ -56,8 +52,8 @@ public class ResultServiceImpl implements ResultService {
.post()
.uri(idToken.getTokenEndpoint().lineitem() + "/scores/result")
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/vnd.ims.lis.v1.score+json")
.header("Accept", "application/vnd.ims.lis.v1.score+json")
.header("Content-Type", CONTENT_TYPE)
.header("Accept", ACCEPT)
.body(body)
.retrieve();

View File

@ -0,0 +1,24 @@
package ru.oa2.lti.application.service.task;
import ru.oa2.lti.domain.model.ResultRequest;
import ru.oa2.lti.domain.model.results.ResultResponse;
import java.util.UUID;
public interface TaskService {
/*
Получение задачи (контекста лабораторной работы)
*/
String getTask(UUID contextId);
/*
Запуск проверки через скрипт
*/
ResultResponse checkTask(ResultRequest resultRequest);
/*
Сохранение/обновление task-а
*/
void saveTask(String initScript, String checkScript, String description);
}

View File

@ -0,0 +1,76 @@
package ru.oa2.lti.application.service.task;
import org.springframework.stereotype.Service;
import ru.oa2.lti.application.infrastructure.lms.LMSService;
import ru.oa2.lti.application.infrastructure.runner.Runner;
import ru.oa2.lti.application.service.results.ResultService;
import ru.oa2.lti.domain.model.ResultRequest;
import ru.oa2.lti.domain.model.task.TaskData;
import ru.oa2.lti.application.infrastructure.repository.LMSContentRepository;
import ru.oa2.lti.application.infrastructure.repository.entities.LMSContent;
import ru.oa2.lti.application.infrastructure.repository.entities.Task;
import ru.oa2.lti.domain.model.results.ResultResponse;
import java.util.Collection;
import java.util.UUID;
@Service
public class TaskServiceImpl implements TaskService {
private final LMSContentRepository lmsContentRepository;
private final Runner runner;
//TODO объединить в один?
final ResultService resultService;
final LMSService lmsService;
public TaskServiceImpl(LMSContentRepository lmsContentRepository,
Runner runner,
ResultService resultService,
LMSService lmsService) {
this.lmsContentRepository = lmsContentRepository;
this.runner = runner;
this.resultService = resultService;
this.lmsService = lmsService;
}
@Override
public String getTask(UUID contextId) {
var content = lmsContentRepository.getLMSContentByContentId(contextId);
if(content.isPresent()) {
LMSContent lmsContent = content.get();
Collection<Task> tasks = lmsContent.getTasks();
//TODO добавить версию в Task и выбирать самую старшую и опубликованную
TaskData data = tasks.stream().findFirst().get().getData();
//TODO string -> exception?
if (!runner.run(contextId, data.getInitScript())) {
return "Init script FAILED";
}
return data.getDescription();
} else {
return "Not Page";
}
}
@Override
public ResultResponse checkTask(ResultRequest resultRequest) {
//TODO запуск скрипта проверки
var assessToken = lmsService.exchangeForAccessToken(
resultRequest.clientId());
resultService.setResult(assessToken, resultRequest.idToken());
//TODO обработка и запуск скрипта
return new ResultResponse("success");
}
@Override
public void saveTask(String initScript, String checkScript, String description) {
//TODO
}
}

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.utils;
package ru.oa2.lti.application.utils;
import java.io.IOException;
import java.nio.file.Files;

View File

@ -1,62 +0,0 @@
package ru.oa2.lti.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
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 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;
@Slf4j
@RestController
@RequestMapping("/tool/lti/result")
public class ResultController {
final ApplicationService service;
final ResultService resultService;
final TokenService tokenService;
final JwtService jwtService;
public ResultController(ApplicationService applicationService,
ResultService resultService,
TokenService tokenService,
JwtService jwtService) {
this.service = applicationService;
this.resultService = resultService;
this.tokenService = tokenService;
this.jwtService = jwtService;
}
@PostMapping("/docker")
public ResponseEntity result(@RequestBody String body,
HttpServletRequest req) {
log.info("RESULT: {}", body);
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());
String idToken = (String) session.getAttribute("id_token");
resultService.setResult(
assessToken,
jwtService.getTokenPayload(idToken)
);
return ResponseEntity
.accepted()
.body(service.saveResult(body));
}
}

View File

@ -1,5 +0,0 @@
package ru.oa2.lti.domain.carecase;
public class GetTask {
}

View File

@ -0,0 +1,8 @@
package ru.oa2.lti.domain.model;
//TODO 2 одинаковых имени? )
public record ResultRequest(
String clientId,
String idToken
) {
}

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.jwt;
package ru.oa2.lti.domain.model.auth;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.jwt;
package ru.oa2.lti.domain.model.auth;
/*
"https://purl.imsglobal.org/spec/lti/claim/launch_presentation" : {

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.model;
package ru.oa2.lti.domain.model.auth;
import lombok.Builder;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.jwt;
package ru.oa2.lti.domain.model.auth;
import java.util.List;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.jwt;
package ru.oa2.lti.domain.model.auth;
import lombok.Data;
import lombok.NoArgsConstructor;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.jwt;
package ru.oa2.lti.domain.model.auth;
import java.util.UUID;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.jwt;
package ru.oa2.lti.domain.model.auth;
import java.util.ArrayList;
import java.util.Arrays;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.jwt;
package ru.oa2.lti.domain.model.auth;
import java.util.List;
import java.util.UUID;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.jwt;
package ru.oa2.lti.domain.model.auth;
import java.util.List;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.results.dto;
package ru.oa2.lti.domain.model.results;
public enum GradingType {
NotStarted,

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.results.dto;
package ru.oa2.lti.domain.model.results;
public record Lineitems(
String id,

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.results.dto;
package ru.oa2.lti.domain.model.results;
public enum ProgressType {
Initialized,

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.results.dto;
package ru.oa2.lti.domain.model.results;
import lombok.Builder;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.service.results.dto;
package ru.oa2.lti.domain.model.results;
public record ResultResponse(
String status

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.model;
package ru.oa2.lti.domain.model.task;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.model;
package ru.oa2.lti.domain.model.task;
public enum TaskQueueStatus {
NEW,

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.model;
package ru.oa2.lti.domain.model.task;
public record TaskResult(
//TODO результат работы(лог), true/false, error:<msg>, оценка

View File

@ -1,4 +1,4 @@
package ru.oa2.lti.model;
package ru.oa2.lti.domain.model.task;
public enum TaskType {
DOCKER,

View File

@ -1,6 +0,0 @@
package ru.oa2.lti.infrastructure;
public interface LMSService {
String ltiAuth(String ltiLoginHint, String iss, String loginHint);
}

View File

@ -1,40 +0,0 @@
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" +
"&lti_message_hint=" + ltiLoginHint)
.retrieve();
ResponseEntity<String> body = result.toEntity(String.class);
if (log.isDebugEnabled()) {
log.debug("lti/auth RESPONSE: {}", body);
}
return body.getBody();
}
}

View File

@ -1,8 +0,0 @@
package ru.oa2.lti.model;
public record ResultRequest(
String taskId,
String userId,
String answer
) {
}

View File

@ -1,7 +0,0 @@
package ru.oa2.lti.model;
public record User(
String clientId,
String role
) {
}

View File

@ -1,8 +0,0 @@
package ru.oa2.lti.service.jwt;
public interface JwtService {
Payload getPayload(String jwt);
IdTokenPayload getTokenPayload(String jwt);
}

View File

@ -1,66 +0,0 @@
package ru.oa2.lti.service.jwt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestTemplate;
import ru.oa2.lti.config.AppProperties;
import java.util.Map;
@Slf4j
@Service
public class TokenService {
private final RestTemplate restTemplate = new RestTemplate();
private final AppProperties appProperties;
public TokenService(AppProperties appProperties) {
this.appProperties = appProperties;
}
// Вызывается после LTI-запуска
public String exchangeForAccessToken(
String clientId) {
var endpointUrl = appProperties.lmsUrl() + "/lti/token";
try {
// Генерируем client_assertion
String clientAssertion = JwtAssertionGenerator.generateClientAssertion(
clientId,
endpointUrl,
"my-key-id-1" // должен совпадать с тем, что в JWKS
);
// Формируем тело запроса
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "client_credentials");
body.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
body.add("client_assertion", clientAssertion);
body.add("scope", Scope.scope(Scope.LINE_ITEM, Scope.SCOPE, Scope.ENDPOINT));
// Заголовки
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
// Отправляем запрос к OpenOLAT
ResponseEntity<Map> response = restTemplate.postForEntity(endpointUrl, request, Map.class);
if (response.getStatusCode() == HttpStatus.OK) {
Map<String, Object> responseBody = response.getBody();
return (String) responseBody.get("access_token");
} else {
throw new RuntimeException("Ошибка получения токена: " + response.getStatusCode());
}
} catch (Exception e) {
throw new RuntimeException("Не удалось получить access token", e);
}
}
}

View File

@ -1,10 +0,0 @@
package ru.oa2.lti.service.results;
import ru.oa2.lti.service.jwt.IdTokenPayload;
import java.util.UUID;
public interface ResultService {
void setResult(String accessToken, IdTokenPayload idToken);
}

View File

@ -1,27 +0,0 @@
package ru.oa2.lti.service.task;
import ru.oa2.lti.model.ResultRequest;
import ru.oa2.lti.model.TaskResult;
import ru.oa2.lti.model.User;
import java.util.UUID;
public interface TaskService {
/*
Получение задачи (контекста лабораторной работы)
*/
//TODO возвращаем объект -> в String отдельный тех.класс сериализации через шаблонизатор
String getTask(String id, User user);
/*
Отправить ответ по задаче
UUID - id - kеу kafka message?
*/
UUID submit(ResultRequest resultRequest);
/*
Проверить результат по UUID отправленного результата
*/
TaskResult getResult(UUID requestId);
}

View File

@ -1,26 +0,0 @@
package ru.oa2.lti.service.task;
import ru.oa2.lti.model.ResultRequest;
import ru.oa2.lti.model.TaskResult;
import ru.oa2.lti.model.User;
import java.util.UUID;
//TODO реализовать методы
public class TaskServiceImpl implements TaskService {
@Override
public String getTask(String id, User user) {
return "";
}
@Override
public UUID submit(ResultRequest resultRequest) {
return null;
}
@Override
public TaskResult getResult(UUID requestId) {
return null;
}
}

View File

@ -13,7 +13,8 @@ document.getElementById('submitBtn').addEventListener('click', function () {
participantId: participantId
};
fetch('/tool/lti/result/docker', {
//TODO payload не нужен, убрать
fetch('/tool/lti/task/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)

View File

@ -1,7 +1,7 @@
package ru.oa2.lti;
import org.junit.jupiter.api.Test;
import ru.oa2.lti.service.jwt.Scope;
import ru.oa2.lti.domain.model.auth.Scope;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

View File

@ -3,8 +3,8 @@ 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 ru.oa2.lti.application.infrastructure.runner.Runner;
import ru.oa2.lti.application.infrastructure.runner.RunnerImpl;
import java.util.UUID;

View File

@ -3,9 +3,9 @@ package ru.oa2.lti.jwt;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import ru.oa2.lti.config.AppConfig;
import ru.oa2.lti.service.jwt.JwtService;
import ru.oa2.lti.service.jwt.JwtServiceImpl;
import ru.oa2.lti.application.config.AppConfig;
import ru.oa2.lti.application.service.jwt.JwtService;
import ru.oa2.lti.application.service.jwt.JwtServiceImpl;
@SpringBootTest(classes = {AppConfig.class, JwtServiceImpl.class})
public class IdTokenTest {