доработка редактора кода
This commit is contained in:
parent
6f42fe0ee6
commit
6e7bf47814
|
|
@ -2,16 +2,21 @@ package ru.oa2.lti.application.controller;
|
|||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
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.RequestBody;
|
||||
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 ru.oa2.lti.domain.model.auth.LtiLogin;
|
||||
import ru.oa2.lti.domain.model.auth.Payload;
|
||||
import ru.oa2.lti.domain.model.results.ResultResponse;
|
||||
import ru.oa2.lti.domain.model.task.RequestUpdateTask;
|
||||
import ru.oa2.lti.domain.model.task.TaskData;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
|
@ -47,21 +52,36 @@ public class TaskController {
|
|||
return "redirect:/error";
|
||||
}
|
||||
|
||||
model.addAttribute("description", data);
|
||||
}
|
||||
model.addAttribute("description", data.getDescription());
|
||||
|
||||
if (Objects.requireNonNull(payload).getCoach()) {
|
||||
model.addAttribute("initScript", data.getInitScript());
|
||||
model.addAttribute("checkScript", data.getVerificationScript());
|
||||
|
||||
return "task-editor";
|
||||
}
|
||||
}
|
||||
|
||||
return "task";
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity updateTask() {
|
||||
public ResponseEntity updateTask(@RequestBody TaskData data,
|
||||
HttpServletRequest request) {
|
||||
|
||||
//TODO exception
|
||||
taskService.saveTask("TODO", "TODO", "TODO");
|
||||
return ResponseEntity.accepted().build();
|
||||
var session = request.getSession();
|
||||
var payload = (Payload) session.getAttribute("payload");
|
||||
|
||||
if (payload != null && payload.getCoach()) {
|
||||
return ResponseEntity.accepted().body(
|
||||
taskService.saveTask(RequestUpdateTask.builder()
|
||||
.contextId(payload.getContextId())
|
||||
.data(data)
|
||||
.build())
|
||||
);
|
||||
}
|
||||
return ResponseEntity.status(HttpStatusCode.valueOf(403))
|
||||
.body(new ResultResponse("forbidden"));
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ package ru.oa2.lti.application.infrastructure.repository.entities;
|
|||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import ru.oa2.lti.domain.model.task.TaskData;
|
||||
import ru.oa2.lti.domain.model.task.TaskType;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "task")
|
||||
public class Task {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package ru.oa2.lti.application.service.task;
|
|||
|
||||
import ru.oa2.lti.domain.model.ResultRequest;
|
||||
import ru.oa2.lti.domain.model.results.ResultResponse;
|
||||
import ru.oa2.lti.domain.model.task.RequestUpdateTask;
|
||||
import ru.oa2.lti.domain.model.task.TaskData;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -10,7 +12,7 @@ public interface TaskService {
|
|||
/*
|
||||
Получение задачи (контекста лабораторной работы)
|
||||
*/
|
||||
String getTask(UUID contextId);
|
||||
TaskData getTask(UUID contextId);
|
||||
|
||||
/*
|
||||
Запуск проверки через скрипт
|
||||
|
|
@ -20,5 +22,5 @@ public interface TaskService {
|
|||
/*
|
||||
Сохранение/обновление task-а
|
||||
*/
|
||||
void saveTask(String initScript, String checkScript, String description);
|
||||
ResultResponse saveTask(RequestUpdateTask requestDataTask);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,25 @@
|
|||
package ru.oa2.lti.application.service.task;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.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.results.ResultResponse;
|
||||
import ru.oa2.lti.domain.model.task.RequestUpdateTask;
|
||||
import ru.oa2.lti.domain.model.task.TaskData;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
public class TaskServiceImpl implements TaskService {
|
||||
|
||||
private final LMSContentRepository lmsContentRepository;
|
||||
|
|
@ -36,7 +41,7 @@ public class TaskServiceImpl implements TaskService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getTask(UUID contextId) {
|
||||
public TaskData getTask(UUID contextId) {
|
||||
var content = lmsContentRepository.getLMSContentByContentId(contextId);
|
||||
|
||||
if(content.isPresent()) {
|
||||
|
|
@ -48,12 +53,12 @@ public class TaskServiceImpl implements TaskService {
|
|||
|
||||
//TODO string -> exception?
|
||||
if (!runner.run(contextId, data.getInitScript())) {
|
||||
return "Init script FAILED";
|
||||
return new TaskData("Init script FAILED", "", "");
|
||||
}
|
||||
|
||||
return data.getDescription();
|
||||
return data;
|
||||
} else {
|
||||
return "Not Page";
|
||||
return new TaskData("Not Page", "", "");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +75,21 @@ public class TaskServiceImpl implements TaskService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void saveTask(String initScript, String checkScript, String description) {
|
||||
//TODO
|
||||
public ResultResponse saveTask(RequestUpdateTask requestUpdateTask) {
|
||||
|
||||
log.info("save");
|
||||
var result = lmsContentRepository.getLMSContentByContentId(requestUpdateTask.getContextId());
|
||||
|
||||
//TODO доработать версионирование task-ов
|
||||
if (result.isPresent()) {
|
||||
var content = result.get();
|
||||
var tasks = content.getTasks();
|
||||
var currentTask = tasks.stream().findFirst();
|
||||
currentTask.ifPresent(task -> task.setData(requestUpdateTask.getData()));
|
||||
lmsContentRepository.save(content);
|
||||
}
|
||||
|
||||
//TODO обработка exception - failed / success
|
||||
return new ResultResponse("success");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.oa2.lti.domain.model.task;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class RequestUpdateTask {
|
||||
UUID contextId;
|
||||
TaskData data;
|
||||
}
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
package ru.oa2.lti.domain.model.task;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TaskData {
|
||||
String initScript;
|
||||
String description;
|
||||
String initScript;
|
||||
String verificationScript;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
document.getElementById('saveBtn').addEventListener('click', async () => {
|
||||
|
||||
const description = document.getElementById('description').value;
|
||||
const initScript = document.getElementById('initScript').value;
|
||||
const verificationScript = document.getElementById('checkScript').value;
|
||||
|
||||
const payload = {
|
||||
description,
|
||||
initScript,
|
||||
verificationScript
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/tool/lti/task', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
const statusContainer = document.getElementById('statusContainer');
|
||||
const statusIcon = document.getElementById('icon');
|
||||
const statusLabel = document.getElementById('statusLabel');
|
||||
|
||||
if (response.ok) {
|
||||
statusIcon.className = 'fa-solid fa-check-circle';
|
||||
statusIcon.classList.add('status-success');
|
||||
statusLabel.textContent = 'Задача успешно сохранена';
|
||||
statusContainer.style.display = 'flex';
|
||||
statusContainer.classList.remove('status-failed');
|
||||
statusContainer.classList.add('status-success');
|
||||
} else {
|
||||
throw new Error(result.message || 'Ошибка сохранения');
|
||||
}
|
||||
} catch (error) {
|
||||
const statusContainer = document.getElementById('statusContainer');
|
||||
const statusIcon = document.getElementById('icon');
|
||||
const statusLabel = document.getElementById('statusLabel');
|
||||
|
||||
statusIcon.className = 'fa-solid fa-times-circle';
|
||||
statusIcon.classList.add('status-failed');
|
||||
statusLabel.textContent = 'Ошибка: ' + error.message;
|
||||
statusContainer.style.display = 'flex';
|
||||
statusContainer.classList.remove('status-success');
|
||||
statusContainer.classList.add('status-failed');
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Доступ запрещён — 403</title>
|
||||
<!-- Bootstrap 5 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Font Awesome -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
color: #495057;
|
||||
}
|
||||
.forbidden-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
padding: 40px;
|
||||
max-width: 500px;
|
||||
text-align: center;
|
||||
border: 1px solid #e0e7ff;
|
||||
}
|
||||
.forbidden-icon {
|
||||
font-size: 70px;
|
||||
color: #dc3545;
|
||||
margin-bottom: 20px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
.error-code {
|
||||
font-size: 100px;
|
||||
font-weight: 700;
|
||||
color: #6c757d;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.error-message {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #212529;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.error-description {
|
||||
font-size: 16px;
|
||||
color: #6c757d;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.back-btn {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
padding: 10px 24px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.back-btn:hover {
|
||||
background-color: #0b5ed7;
|
||||
color: white;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="forbidden-card">
|
||||
<!-- Иконка "запрещено" -->
|
||||
<div class="forbidden-icon">
|
||||
<i class="fas fa-ban"></i>
|
||||
</div>
|
||||
|
||||
<!-- Код ошибки -->
|
||||
<div class="error-code">403</div>
|
||||
|
||||
<!-- Основное сообщение -->
|
||||
<div class="error-message">Доступ запрещён</div>
|
||||
|
||||
<!-- Описание -->
|
||||
<p class="error-description" th:text="${message}">
|
||||
У вас нет прав для просмотра этой страницы.
|
||||
Пожалуйста, обратитесь к администратору, если считаете, что это ошибка.
|
||||
</p>
|
||||
|
||||
<!-- Кнопка "Назад" -->
|
||||
<a th:href="@{${redirectUrl} ?: '/'}" class="back-btn">
|
||||
<i class="fas fa-arrow-left"></i> Вернуться назад
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS (опционально) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Редактор задачи LTI</title>
|
||||
<title>Редактор задачи LTI #TODO подтягивать name?</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
|
||||
|
|
@ -115,19 +115,28 @@
|
|||
<!-- Поле: Описание задачи (редактируемое) -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">Описание задачи (HTML)</label>
|
||||
<textarea id="description" class="code-area" placeholder="Введите HTML-описание задачи..."><th:block th:utext="${description ?: ''}"/></textarea>
|
||||
<textarea
|
||||
id="description"
|
||||
class="code-area"
|
||||
placeholder="Введите HTML-описание задачи..."><th:block th:utext="${description ?: ''}"/></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Поле: initScript -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">initScript (JavaScript)</label>
|
||||
<textarea id="initScript" class="code-area" placeholder="JavaScript-код для инициализации среды..."><th:block th:text="${initScript ?: ''}"/></textarea>
|
||||
<label class="form-label">initScript</label>
|
||||
<textarea
|
||||
id="initScript"
|
||||
class="code-area"
|
||||
placeholder="Скрипт инициализации задания"><th:block th:text="${initScript ?: ''}"/></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Поле: checkScript -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">checkScript (JavaScript)</label>
|
||||
<textarea id="checkScript" class="code-area" placeholder="JavaScript-код для проверки результата..."><th:block th:text="${checkScript ?: ''}"/></textarea>
|
||||
<label class="form-label">checkScript</label>
|
||||
<textarea
|
||||
id="checkScript"
|
||||
class="code-area"
|
||||
placeholder="Скрипт проверки задания"><th:block th:text="${checkScript ?: ''}"/></textarea>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -148,69 +157,17 @@
|
|||
<i class="fas fa-save"></i> Сохранить задачу
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button id="submitBtn" class="btn btn-primary submit-btn">
|
||||
<i class="fas fa-paper-plane"></i> Отправить на проверку
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Скрытые поля -->
|
||||
<input type="hidden" id="contextId" th:value="${contextId}" />
|
||||
<input type="hidden" id="participantId" th:value="${participantId}" />
|
||||
|
||||
<!-- Скрипт отправки -->
|
||||
<script th:src="@{/tool/lti/js/main.js}"></script>
|
||||
<script>
|
||||
document.getElementById('saveBtn').addEventListener('click', async () => {
|
||||
const contextId = document.getElementById('contextId').value;
|
||||
const description = document.getElementById('description').value;
|
||||
const initScript = document.getElementById('initScript').value;
|
||||
const checkScript = document.getElementById('checkScript').value;
|
||||
|
||||
const payload = {
|
||||
contextId,
|
||||
description,
|
||||
initScript,
|
||||
checkScript
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/tool/lti/task', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
const statusContainer = document.getElementById('statusContainer');
|
||||
const statusIcon = document.getElementById('icon');
|
||||
const statusLabel = document.getElementById('statusLabel');
|
||||
|
||||
if (response.ok) {
|
||||
statusIcon.className = 'fa-solid fa-check-circle';
|
||||
statusIcon.classList.add('status-success');
|
||||
statusLabel.textContent = 'Задача успешно сохранена';
|
||||
statusContainer.style.display = 'flex';
|
||||
statusContainer.classList.remove('status-failed');
|
||||
statusContainer.classList.add('status-success');
|
||||
} else {
|
||||
throw new Error(result.message || 'Ошибка сохранения');
|
||||
}
|
||||
} catch (error) {
|
||||
const statusContainer = document.getElementById('statusContainer');
|
||||
const statusIcon = document.getElementById('icon');
|
||||
const statusLabel = document.getElementById('statusLabel');
|
||||
|
||||
statusIcon.className = 'fa-solid fa-times-circle';
|
||||
statusIcon.classList.add('status-failed');
|
||||
statusLabel.textContent = 'Ошибка: ' + error.message;
|
||||
statusContainer.style.display = 'flex';
|
||||
statusContainer.classList.remove('status-success');
|
||||
statusContainer.classList.add('status-failed');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script th:src="@{/tool/lti/js/edit.js}"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue