diff --git a/docs/schema-database.puml b/docs/schema-database.puml
new file mode 100644
index 0000000..d057734
--- /dev/null
+++ b/docs/schema-database.puml
@@ -0,0 +1,137 @@
+@startuml PostgreSQL Database Schema
+
+!define PRIMARY_KEY(x) <&key> x
+!define FOREIGN_KEY(x) <&link-intact> x
+!define COLUMN(x) x
+!define INDEX(x) <&magnifying-glass> x
+
+scale 1024 width
+
+skinparam linetype ortho
+skinparam roundcorner 10
+skinparam shadowing true
+
+skinparam entity {
+ BackgroundColor #FFFEF0
+ BorderColor #2F4F4F
+ ArrowColor #2F4F4F
+ FontColor #333333
+ FontSize 12
+ AttributeFontSize 11
+}
+
+skinparam note {
+ BackgroundColor #FFFFCC
+ BorderColor #999999
+}
+
+package "External Tool schema" {
+
+ entity "**history**" as history {
+ .. Первичный ключ ..
+ PRIMARY_KEY(id) : int8 <>
+ ----
+ .. Атрибуты ..
+ COLUMN(deployment_id) : uuid [NOT NULL]
+ COLUMN(content_uuid) : uuid [NOT NULL]
+ COLUMN(created) : timestamp [NOT NULL]
+ COLUMN(message) : varchar(2048) [NOT NULL]
+ ----
+ .. Ограничения ..
+ <&check> pk_history (PRIMARY KEY)
+ }
+
+ entity "**lms_content**" as lms_content {
+ .. Первичный ключ ..
+ PRIMARY_KEY(id) : int8 <>
+ ----
+ .. Атрибуты ..
+ COLUMN(deployment_id) : uuid [NOT NULL]
+ INDEX(content_uuid) : uuid [NOT NULL]
+ COLUMN(created) : timestamp [NOT NULL]
+ FOREIGN_KEY(task_id) : int8 [NOT NULL]
+ ----
+ .. Ограничения ..
+ <&check> pk_lms_content (PRIMARY KEY)
+ <&link-intact> fk_lms_content_task (FOREIGN KEY)
+ ----
+ .. Индексы ..
+ <&list> idx_content_uuid (content_uuid)
+ }
+
+ entity "**task**" as task {
+ .. Первичный ключ ..
+ PRIMARY_KEY(id) : int8 <>
+ ----
+ .. Атрибуты (обязательные) ..
+ COLUMN(name) : varchar(250) [NOT NULL]
+ COLUMN(description) : text [NOT NULL]
+ ----
+ .. Атрибуты (опциональные) ..
+ COLUMN(init_script) : text [NULL]
+ COLUMN(verification_script) : text [NULL]
+ COLUMN(delete_script) : text [NULL]
+ ----
+ .. Ограничения ..
+ <&check> pk_task (PRIMARY KEY)
+ }
+
+}
+
+' === СВЯЗИ ===
+lms_content }o--|| task : " fk_lms_content_task\n (task_id → id) "
+
+' === ЛОГИЧЕСКИЕ СВЯЗИ (пунктиром) ===
+history ..> lms_content : " content_uuid, deployment_id "
+
+' === ПРИМЕЧАНИЯ ===
+note right of history
+ **Таблица истории**
+ ════════════════════
+ Хранит историю изменений
+ и событий в системе.
+
+ Назначение полей:
+ • deployment_id - ID развёртывания
+ • content_uuid - UUID контента
+ • created - время создания
+ • message - текст сообщения
+
+ ✓ Все поля обязательные
+ ⚠ Нет явных FK связей
+end note
+
+note left of task
+ **Таблица лабораторных работ**
+ ════════════════════
+ Хранит определения задач
+ для системы обучения.
+
+ Обязательные поля:
+ • name - название задачи
+ • description - описание
+
+ Скрипты (опциональные):
+ • init_script - инициализация
+ • verification_script - проверка
+ • delete_script - очистка
+end note
+
+note left of lms_content
+ **Связь контента LMS с Task**
+ ════════════════════
+ Связь контента из LMS с
+ лабораторными работами в
+ External Tool
+
+ Индексы:
+ • idx_content_uuid для
+ быстрого поиска по UUID
+
+ Связи:
+ • task_id → task.id (N:1)
+
+ ✓ Все поля обязательные
+end note
+
+@enduml
\ No newline at end of file
diff --git a/docs/schema-db-2.puml b/docs/schema-db-2.puml
new file mode 100644
index 0000000..058775c
--- /dev/null
+++ b/docs/schema-db-2.puml
@@ -0,0 +1,120 @@
+@startuml PostgreSQL Database Schema
+
+!define PRIMARY_KEY(x) <&key> x
+!define FOREIGN_KEY(x) <&link-intact> x
+!define COLUMN(x) x
+!define INDEX(x) <&magnifying-glass> x
+
+scale 1024 width
+
+skinparam linetype ortho
+skinparam roundcorner 10
+skinparam shadowing true
+
+skinparam entity {
+ BackgroundColor #FFFEF0
+ BorderColor #2F4F4F
+ ArrowColor #2F4F4F
+ FontColor #333333
+ FontSize 12
+ AttributeFontSize 11
+}
+
+skinparam note {
+ BackgroundColor #FFFFCC
+ BorderColor #999999
+}
+
+package "public schema" {
+
+ entity "**history**" as history {
+ .. Первичный ключ ..
+ PRIMARY_KEY(id) : int8 <>
+ ----
+ .. Атрибуты ..
+ COLUMN(deployment_id) : uuid [NULL]
+ COLUMN(content_uuid) : uuid [NULL]
+ COLUMN(created) : timestamp [NULL]
+ COLUMN(message) : varchar(2048) [NULL]
+ ----
+ .. Ограничения ..
+ <&check> pk_history (PRIMARY KEY)
+ }
+
+ entity "**lms_content**" as lms_content {
+ .. Первичный ключ ..
+ PRIMARY_KEY(id) : int8 <>
+ ----
+ .. Атрибуты ..
+ COLUMN(deployment_id) : uuid [NULL]
+ INDEX(content_uuid) : uuid [NULL]
+ COLUMN(created) : timestamp [NULL]
+ FOREIGN_KEY(task_id) : int8 [NULL]
+ ----
+ .. Ограничения ..
+ <&check> pk_lms_content (PRIMARY KEY)
+ <&link-intact> fk_lms_content_task (FOREIGN KEY)
+ ----
+ .. Индексы ..
+ <&list> idx_content_uuid (content_uuid)
+ }
+
+ entity "**task**" as task {
+ .. Первичный ключ ..
+ PRIMARY_KEY(id) : int8 <>
+ ----
+ .. Атрибуты ..
+ COLUMN(data) : jsonb [NULL]
+ ----
+ .. Ограничения ..
+ <&check> pk_task (PRIMARY KEY)
+ }
+
+}
+
+' === СВЯЗИ ===
+lms_content }o--|| task : " fk_lms_content_task\n (task_id → id) "
+
+' === ЛОГИЧЕСКИЕ СВЯЗИ (пунктиром) ===
+history ..> lms_content : " логическая связь\n по content_uuid "
+
+' === ПРИМЕЧАНИЯ ===
+note right of history
+ **Таблица истории**
+ ════════════════════
+ Хранит историю изменений
+ и событий в системе.
+
+ Назначение полей:
+ • deployment_id - ID развёртывания
+ • content_uuid - UUID контента
+ • message - текст сообщения
+
+ ⚠ Нет явных FK связей
+end note
+
+note bottom of task
+ **Таблица задач**
+ ════════════════════
+ Хранит задачи в формате JSON.
+
+ Поле data (jsonb):
+ Гибкая структура для хранения
+ произвольных данных задачи.
+end note
+
+note left of lms_content
+ **Контент LMS**
+ ════════════════════
+ Основная таблица контента
+ системы обучения.
+
+ Индексы:
+ • idx_content_uuid для
+ быстрого поиска по UUID
+
+ Связи:
+ • task_id → task.id (N:1)
+end note
+
+@enduml
\ No newline at end of file
diff --git a/docs/schema-db.puml b/docs/schema-db.puml
new file mode 100644
index 0000000..5b25991
--- /dev/null
+++ b/docs/schema-db.puml
@@ -0,0 +1,44 @@
+@startuml
+!define table(x) entity x << (T,#FFAAAA) >>
+!define pk(x) x
+!define fk(x) x
+
+skinparam entity {
+ BackgroundColor #F5F5F5
+ BorderColor #333333
+}
+
+table(history) {
+ pk(id) : int8 <>
+ --
+ deployment_id : uuid
+ content_uuid : uuid
+ created : timestamp
+ message : varchar(2048)
+}
+
+table(lms_content) {
+ pk(id) : int8 <>
+ --
+ deployment_id : uuid
+ content_uuid : uuid <>
+ created : timestamp
+ fk(task_id) : int8
+}
+
+table(task) {
+ pk(id) : int8 <>
+ --
+ data : jsonb
+}
+
+lms_content }o--|| task : "task_id → id"
+
+note bottom of history
+ Нет явных FK связей,
+ но content_uuid может
+ логически связываться
+ с lms_content.content_uuid
+end note
+
+@enduml
\ No newline at end of file
diff --git a/src/main/java/ru/oa2/lti/application/controller/TaskController.java b/src/main/java/ru/oa2/lti/application/controller/TaskController.java
index 1210187..1362f07 100644
--- a/src/main/java/ru/oa2/lti/application/controller/TaskController.java
+++ b/src/main/java/ru/oa2/lti/application/controller/TaskController.java
@@ -52,11 +52,13 @@ public class TaskController {
return "redirect:/error";
}
+ model.addAttribute("name", data.getName());
model.addAttribute("description", data.getDescription());
if (Objects.requireNonNull(payload).getCoach()) {
model.addAttribute("initScript", data.getInitScript());
model.addAttribute("checkScript", data.getVerificationScript());
+ model.addAttribute("deleteScript", data.getDeleteScript());
return "task-editor";
}
diff --git a/src/main/java/ru/oa2/lti/application/infrastructure/repository/LMSContentRepository.java b/src/main/java/ru/oa2/lti/application/infrastructure/repository/LMSContentRepository.java
index c309277..f8f70cd 100644
--- a/src/main/java/ru/oa2/lti/application/infrastructure/repository/LMSContentRepository.java
+++ b/src/main/java/ru/oa2/lti/application/infrastructure/repository/LMSContentRepository.java
@@ -10,5 +10,5 @@ import java.util.UUID;
@Repository
public interface LMSContentRepository extends JpaRepository {
- Optional getLMSContentByContentId(UUID contentId);
+ Optional getLMSContentByContentUuid(UUID contentUuid);
}
diff --git a/src/main/java/ru/oa2/lti/application/infrastructure/repository/TaskQueueRepository.java b/src/main/java/ru/oa2/lti/application/infrastructure/repository/TaskQueueRepository.java
deleted file mode 100644
index 2b9b572..0000000
--- a/src/main/java/ru/oa2/lti/application/infrastructure/repository/TaskQueueRepository.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package ru.oa2.lti.application.infrastructure.repository;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
-import ru.oa2.lti.application.infrastructure.repository.entities.TaskQueue;
-
-@Repository
-public interface TaskQueueRepository extends JpaRepository {
-}
diff --git a/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/History.java b/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/History.java
new file mode 100644
index 0000000..8a0fe48
--- /dev/null
+++ b/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/History.java
@@ -0,0 +1,31 @@
+package ru.oa2.lti.application.infrastructure.repository.entities;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "history")
+public class History {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ long id;
+
+ @Column(name = "deployment_id", nullable = false)
+ UUID deploymentId;
+
+ @Column(name = "content_uuid", nullable = false)
+ UUID contentUuid;
+
+ @Column(name = "created", nullable = false)
+ LocalDateTime created;
+
+ @Column(name = "message", nullable = false, length = 2048)
+ String message;
+}
diff --git a/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/LMSContent.java b/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/LMSContent.java
index cd3b7bb..ff9f663 100644
--- a/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/LMSContent.java
+++ b/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/LMSContent.java
@@ -2,28 +2,34 @@ package ru.oa2.lti.application.infrastructure.repository.entities;
import jakarta.persistence.*;
import lombok.Getter;
+import lombok.Setter;
-import java.util.Collection;
+import java.time.LocalDateTime;
import java.util.UUID;
@Getter
+@Setter
@Entity
-@Table(name = "lms_content")
+@Table(name = "lms_content",
+indexes = {
+ @Index(name = "idx_content_uuid", columnList = "content_uuid")
+})
public class LMSContent {
@Id
- @GeneratedValue(strategy = GenerationType.AUTO)
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
long id;
- @Column(name = "content_key")
- long contentKey;
-
- @Column(name = "content_uuid")
- UUID contentId;
-
- @Column(name = "deployment_id")
+ @Column(name = "deployment_id", nullable = false)
UUID deploymentId;
- @OneToMany(mappedBy="id", fetch = FetchType.LAZY)
- Collection tasks;
+ @Column(name = "content_uuid", nullable = false)
+ UUID contentUuid;
+
+ @Column(name = "created", nullable = false)
+ LocalDateTime created;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "task_id", nullable = false, foreignKey = @ForeignKey(name = "fk_lms_content_task"))
+ Task task;
}
diff --git a/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/Task.java b/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/Task.java
index 377d1ce..0920297 100644
--- a/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/Task.java
+++ b/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/Task.java
@@ -3,10 +3,6 @@ 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
@@ -15,14 +11,21 @@ import ru.oa2.lti.domain.model.task.TaskType;
public class Task {
@Id
- @GeneratedValue(strategy = GenerationType.AUTO)
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
long id;
- @Column(name = "task_type", length = 20)
- @Enumerated(EnumType.STRING)
- TaskType taskType;
+ @Column(name = "name", nullable = false, length = 250)
+ String name;
- @JdbcTypeCode(SqlTypes.JSON)
- @Column(name = "data", columnDefinition = "jsonb")
- TaskData data;
+ @Column(name = "description", nullable = false, columnDefinition = "text")
+ String description;
+
+ @Column(name = "init_script", columnDefinition = "text")
+ String initScript;
+
+ @Column(name = "verification_script", columnDefinition = "text")
+ String verificationScript;
+
+ @Column(name = "delete_script", columnDefinition = "text")
+ String deleteScript;
}
diff --git a/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/TaskQueue.java b/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/TaskQueue.java
deleted file mode 100644
index ceaac58..0000000
--- a/src/main/java/ru/oa2/lti/application/infrastructure/repository/entities/TaskQueue.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package ru.oa2.lti.application.infrastructure.repository.entities;
-
-import jakarta.persistence.*;
-import lombok.Getter;
-import ru.oa2.lti.domain.model.task.TaskQueueStatus;
-
-@Getter
-@Entity
-@Table(name = "task_queue")
-public class TaskQueue {
-
- @Id
- @GeneratedValue(strategy = GenerationType.AUTO)
- Long id;
-
- @ManyToOne
- @JoinColumn(name = "task_id", nullable = false)
- Task taskId;
-
- @Column(name = "content", columnDefinition = "jsonb")
- Object content;
-
- @Column(name = "status")
- @Enumerated(EnumType.STRING)
- TaskQueueStatus status;
-
- @Column(name = "finished")
- Boolean finished;
-}
diff --git a/src/main/java/ru/oa2/lti/application/infrastructure/runner/Runner.java b/src/main/java/ru/oa2/lti/application/infrastructure/runner/Runner.java
index 9983e37..9972257 100644
--- a/src/main/java/ru/oa2/lti/application/infrastructure/runner/Runner.java
+++ b/src/main/java/ru/oa2/lti/application/infrastructure/runner/Runner.java
@@ -7,4 +7,6 @@ public interface Runner {
boolean run(UUID userId, String script);
boolean check(UUID userId, String script);
+
+ boolean delete(UUID userId, String script);
}
diff --git a/src/main/java/ru/oa2/lti/application/infrastructure/runner/RunnerImpl.java b/src/main/java/ru/oa2/lti/application/infrastructure/runner/RunnerImpl.java
index 0609b08..44fab8e 100644
--- a/src/main/java/ru/oa2/lti/application/infrastructure/runner/RunnerImpl.java
+++ b/src/main/java/ru/oa2/lti/application/infrastructure/runner/RunnerImpl.java
@@ -39,6 +39,12 @@ public class RunnerImpl implements Runner {
}
}
+ @Override
+ public boolean delete(UUID userId, String script) {
+ //TODO реализовать метод удаления сущностей
+ return false;
+ }
+
private boolean runScript(UUID userId, String script) throws Exception {
var processBuilder = new ProcessBuilder();
diff --git a/src/main/java/ru/oa2/lti/application/service/task/TaskServiceImpl.java b/src/main/java/ru/oa2/lti/application/service/task/TaskServiceImpl.java
index 245cbc0..40e6a4c 100644
--- a/src/main/java/ru/oa2/lti/application/service/task/TaskServiceImpl.java
+++ b/src/main/java/ru/oa2/lti/application/service/task/TaskServiceImpl.java
@@ -14,7 +14,6 @@ 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
@@ -42,23 +41,26 @@ public class TaskServiceImpl implements TaskService {
@Override
public TaskData getTask(UUID contextId) {
- var content = lmsContentRepository.getLMSContentByContentId(contextId);
+ var content = lmsContentRepository.getLMSContentByContentUuid(contextId);
if(content.isPresent()) {
LMSContent lmsContent = content.get();
- Collection tasks = lmsContent.getTasks();
+ Task task = lmsContent.getTask();
- //TODO добавить версию в Task и выбирать самую старшую и опубликованную
- TaskData data = tasks.stream().findFirst().get().getData();
+ if (task == null) {
+ return new TaskData("Not Page", "", "", "", "");
+ }
+
+ TaskData data = entityToTaskData(task);
//TODO string -> exception?
if (!runner.run(contextId, data.getInitScript())) {
- return new TaskData("Init script FAILED", "", "");
+ return new TaskData("Init script FAILED", "", "", "", "");
}
return data;
} else {
- return new TaskData("Not Page", "", "");
+ return new TaskData("Not Page", "", "", "", "");
}
}
@@ -78,18 +80,38 @@ public class TaskServiceImpl implements TaskService {
public ResultResponse saveTask(RequestUpdateTask requestUpdateTask) {
log.info("save");
- var result = lmsContentRepository.getLMSContentByContentId(requestUpdateTask.getContextId());
+ var result = lmsContentRepository.getLMSContentByContentUuid(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);
+ LMSContent content = result.get();
+ Task task = content.getTask();
+
+ if (task != null) {
+ updateTaskFromData(task, requestUpdateTask.getData());
+ lmsContentRepository.save(content);
+ }
}
//TODO обработка exception - failed / success
return new ResultResponse("success");
}
+
+ private TaskData entityToTaskData(Task task) {
+ return new TaskData(
+ task.getName(),
+ task.getDescription(),
+ task.getInitScript(),
+ task.getVerificationScript(),
+ task.getDeleteScript()
+ );
+ }
+
+ private void updateTaskFromData(Task task, TaskData data) {
+ task.setName(data.getName());
+ task.setDescription(data.getDescription());
+ task.setInitScript(data.getInitScript());
+ task.setVerificationScript(data.getVerificationScript());
+ task.setDeleteScript(data.getDeleteScript());
+ }
}
diff --git a/src/main/java/ru/oa2/lti/domain/model/task/TaskData.java b/src/main/java/ru/oa2/lti/domain/model/task/TaskData.java
index 7135db9..daeaf07 100644
--- a/src/main/java/ru/oa2/lti/domain/model/task/TaskData.java
+++ b/src/main/java/ru/oa2/lti/domain/model/task/TaskData.java
@@ -8,7 +8,9 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
public class TaskData {
+ String name;
String description;
String initScript;
String verificationScript;
+ String deleteScript;
}
diff --git a/src/main/resources/db/changelog/1.0.0/2025-12-add_task_queue.xml b/src/main/resources/db/changelog/1.0.0/2025-12-add_history.xml
similarity index 51%
rename from src/main/resources/db/changelog/1.0.0/2025-12-add_task_queue.xml
rename to src/main/resources/db/changelog/1.0.0/2025-12-add_history.xml
index a87d55e..4cf23fe 100644
--- a/src/main/resources/db/changelog/1.0.0/2025-12-add_task_queue.xml
+++ b/src/main/resources/db/changelog/1.0.0/2025-12-add_history.xml
@@ -8,29 +8,29 @@
-
+
-
+
-
+
+
+
-
+
+
+
-
+
+
+
-
+
+
+
-
-
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/1.0.0/2025-12-add_lms_content.xml b/src/main/resources/db/changelog/1.0.0/2025-12-add_lms_content.xml
index 70b82d7..a285b66 100644
--- a/src/main/resources/db/changelog/1.0.0/2025-12-add_lms_content.xml
+++ b/src/main/resources/db/changelog/1.0.0/2025-12-add_lms_content.xml
@@ -14,13 +14,21 @@
-
+
+
+
-
+
+
+
-
+
+
+
-
+
+
+
@@ -32,5 +40,11 @@
referencedColumnNames="id"
onDelete="NO ACTION"/>
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/1.0.0/2025-12-add_task.xml b/src/main/resources/db/changelog/1.0.0/2025-12-add_task.xml
index efef0d5..dff1bd5 100644
--- a/src/main/resources/db/changelog/1.0.0/2025-12-add_task.xml
+++ b/src/main/resources/db/changelog/1.0.0/2025-12-add_task.xml
@@ -14,9 +14,19 @@
-
+
+
+
-
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml
index 4bc73a2..0e5f394 100644
--- a/src/main/resources/db/changelog/changelog-master.xml
+++ b/src/main/resources/db/changelog/changelog-master.xml
@@ -10,6 +10,6 @@
-
+
\ No newline at end of file