Обновление кодовой базы, после изменения схемы БД

This commit is contained in:
Anton Dzyk 2026-01-09 17:17:29 +03:00
parent 6e7bf47814
commit 11b8f6aa82
18 changed files with 457 additions and 96 deletions

137
docs/schema-database.puml Normal file
View File

@ -0,0 +1,137 @@
@startuml PostgreSQL Database Schema
!define PRIMARY_KEY(x) <b><color:#b8860b><&key></color> x</b>
!define FOREIGN_KEY(x) <color:#aaaaaa><&link-intact></color> <i>x</i>
!define COLUMN(x) <color:#588157>x</color>
!define INDEX(x) <color:#1e90ff><&magnifying-glass></color> 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 <<IDENTITY>>
----
.. Атрибуты ..
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 <<IDENTITY>>
----
.. Атрибуты ..
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 <<IDENTITY>>
----
.. Атрибуты (обязательные) ..
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 : " <size:10>content_uuid, deployment_id "
' === ПРИМЕЧАНИЯ ===
note right of history
**Таблица истории**
════════════════════
Хранит историю изменений
и событий в системе.
<b>Назначение полей:</b>
• deployment_id - ID развёртывания
• content_uuid - UUID контента
• created - время создания
• message - текст сообщения
<color:#006400>✓ Все поля обязательные</color>
<color:#cc0000>⚠ Нет явных FK связей</color>
end note
note left of task
**Таблица лабораторных работ**
════════════════════
Хранит определения задач
для системы обучения.
<b>Обязательные поля:</b>
• name - название задачи
• description - описание
<b>Скрипты (опциональные):</b>
• init_script - инициализация
• verification_script - проверка
• delete_script - очистка
end note
note left of lms_content
**Связь контента LMS с Task**
════════════════════
Связь контента из LMS с
лабораторными работами в
External Tool
<b>Индексы:</b>
• idx_content_uuid для
быстрого поиска по UUID
<b>Связи:</b>
• task_id → task.id (N:1)
<color:#006400>✓ Все поля обязательные</color>
end note
@enduml

120
docs/schema-db-2.puml Normal file
View File

@ -0,0 +1,120 @@
@startuml PostgreSQL Database Schema
!define PRIMARY_KEY(x) <b><color:#b8860b><&key></color> x</b>
!define FOREIGN_KEY(x) <color:#aaaaaa><&link-intact></color> <i>x</i>
!define COLUMN(x) <color:#588157>x</color>
!define INDEX(x) <color:#1e90ff><&magnifying-glass></color> 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 <<IDENTITY>>
----
.. Атрибуты ..
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 <<IDENTITY>>
----
.. Атрибуты ..
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 <<IDENTITY>>
----
.. Атрибуты ..
COLUMN(data) : jsonb [NULL]
----
.. Ограничения ..
<&check> pk_task (PRIMARY KEY)
}
}
' === СВЯЗИ ===
lms_content }o--|| task : " fk_lms_content_task\n (task_id → id) "
' === ЛОГИЧЕСКИЕ СВЯЗИ (пунктиром) ===
history ..> lms_content : " <size:10>логическая связь\n по content_uuid</size> "
' === ПРИМЕЧАНИЯ ===
note right of history
**Таблица истории**
════════════════════
Хранит историю изменений
и событий в системе.
<b>Назначение полей:</b>
• deployment_id - ID развёртывания
• content_uuid - UUID контента
• message - текст сообщения
<color:#cc0000>⚠ Нет явных FK связей</color>
end note
note bottom of task
**Таблица задач**
════════════════════
Хранит задачи в формате JSON.
<b>Поле data (jsonb):</b>
Гибкая структура для хранения
произвольных данных задачи.
end note
note left of lms_content
**Контент LMS**
════════════════════
Основная таблица контента
системы обучения.
<b>Индексы:</b>
• idx_content_uuid для
быстрого поиска по UUID
<b>Связи:</b>
• task_id → task.id (N:1)
end note
@enduml

44
docs/schema-db.puml Normal file
View File

@ -0,0 +1,44 @@
@startuml
!define table(x) entity x << (T,#FFAAAA) >>
!define pk(x) <b><u>x</u></b>
!define fk(x) <i>x</i>
skinparam entity {
BackgroundColor #F5F5F5
BorderColor #333333
}
table(history) {
pk(id) : int8 <<IDENTITY>>
--
deployment_id : uuid
content_uuid : uuid
created : timestamp
message : varchar(2048)
}
table(lms_content) {
pk(id) : int8 <<IDENTITY>>
--
deployment_id : uuid
content_uuid : uuid <<INDEX>>
created : timestamp
fk(task_id) : int8
}
table(task) {
pk(id) : int8 <<IDENTITY>>
--
data : jsonb
}
lms_content }o--|| task : "task_id → id"
note bottom of history
Нет явных FK связей,
но content_uuid может
логически связываться
с lms_content.content_uuid
end note
@enduml

View File

@ -52,11 +52,13 @@ public class TaskController {
return "redirect:/error"; return "redirect:/error";
} }
model.addAttribute("name", data.getName());
model.addAttribute("description", data.getDescription()); model.addAttribute("description", data.getDescription());
if (Objects.requireNonNull(payload).getCoach()) { if (Objects.requireNonNull(payload).getCoach()) {
model.addAttribute("initScript", data.getInitScript()); model.addAttribute("initScript", data.getInitScript());
model.addAttribute("checkScript", data.getVerificationScript()); model.addAttribute("checkScript", data.getVerificationScript());
model.addAttribute("deleteScript", data.getDeleteScript());
return "task-editor"; return "task-editor";
} }

View File

@ -10,5 +10,5 @@ import java.util.UUID;
@Repository @Repository
public interface LMSContentRepository extends JpaRepository<LMSContent, Long> { public interface LMSContentRepository extends JpaRepository<LMSContent, Long> {
Optional<LMSContent> getLMSContentByContentId(UUID contentId); Optional<LMSContent> getLMSContentByContentUuid(UUID contentUuid);
} }

View File

@ -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<TaskQueue, Long> {
}

View File

@ -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;
}

View File

@ -2,28 +2,34 @@ package ru.oa2.lti.application.infrastructure.repository.entities;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import java.util.Collection; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
@Getter @Getter
@Setter
@Entity @Entity
@Table(name = "lms_content") @Table(name = "lms_content",
indexes = {
@Index(name = "idx_content_uuid", columnList = "content_uuid")
})
public class LMSContent { public class LMSContent {
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO) @GeneratedValue(strategy = GenerationType.IDENTITY)
long id; long id;
@Column(name = "content_key") @Column(name = "deployment_id", nullable = false)
long contentKey;
@Column(name = "content_uuid")
UUID contentId;
@Column(name = "deployment_id")
UUID deploymentId; UUID deploymentId;
@OneToMany(mappedBy="id", fetch = FetchType.LAZY) @Column(name = "content_uuid", nullable = false)
Collection<Task> tasks; 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;
} }

View File

@ -3,10 +3,6 @@ package ru.oa2.lti.application.infrastructure.repository.entities;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; 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 @Getter
@Setter @Setter
@ -15,14 +11,21 @@ import ru.oa2.lti.domain.model.task.TaskType;
public class Task { public class Task {
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO) @GeneratedValue(strategy = GenerationType.IDENTITY)
long id; long id;
@Column(name = "task_type", length = 20) @Column(name = "name", nullable = false, length = 250)
@Enumerated(EnumType.STRING) String name;
TaskType taskType;
@JdbcTypeCode(SqlTypes.JSON) @Column(name = "description", nullable = false, columnDefinition = "text")
@Column(name = "data", columnDefinition = "jsonb") String description;
TaskData data;
@Column(name = "init_script", columnDefinition = "text")
String initScript;
@Column(name = "verification_script", columnDefinition = "text")
String verificationScript;
@Column(name = "delete_script", columnDefinition = "text")
String deleteScript;
} }

View File

@ -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;
}

View File

@ -7,4 +7,6 @@ public interface Runner {
boolean run(UUID userId, String script); boolean run(UUID userId, String script);
boolean check(UUID userId, String script); boolean check(UUID userId, String script);
boolean delete(UUID userId, String script);
} }

View File

@ -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 { private boolean runScript(UUID userId, String script) throws Exception {
var processBuilder = new ProcessBuilder(); var processBuilder = new ProcessBuilder();

View File

@ -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.RequestUpdateTask;
import ru.oa2.lti.domain.model.task.TaskData; import ru.oa2.lti.domain.model.task.TaskData;
import java.util.Collection;
import java.util.UUID; import java.util.UUID;
@Slf4j @Slf4j
@ -42,23 +41,26 @@ public class TaskServiceImpl implements TaskService {
@Override @Override
public TaskData getTask(UUID contextId) { public TaskData getTask(UUID contextId) {
var content = lmsContentRepository.getLMSContentByContentId(contextId); var content = lmsContentRepository.getLMSContentByContentUuid(contextId);
if(content.isPresent()) { if(content.isPresent()) {
LMSContent lmsContent = content.get(); LMSContent lmsContent = content.get();
Collection<Task> tasks = lmsContent.getTasks(); Task task = lmsContent.getTask();
//TODO добавить версию в Task и выбирать самую старшую и опубликованную if (task == null) {
TaskData data = tasks.stream().findFirst().get().getData(); return new TaskData("Not Page", "", "", "", "");
}
TaskData data = entityToTaskData(task);
//TODO string -> exception? //TODO string -> exception?
if (!runner.run(contextId, data.getInitScript())) { if (!runner.run(contextId, data.getInitScript())) {
return new TaskData("Init script FAILED", "", ""); return new TaskData("Init script FAILED", "", "", "", "");
} }
return data; return data;
} else { } 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) { public ResultResponse saveTask(RequestUpdateTask requestUpdateTask) {
log.info("save"); log.info("save");
var result = lmsContentRepository.getLMSContentByContentId(requestUpdateTask.getContextId()); var result = lmsContentRepository.getLMSContentByContentUuid(requestUpdateTask.getContextId());
//TODO доработать версионирование task-ов //TODO доработать версионирование task-ов
if (result.isPresent()) { if (result.isPresent()) {
var content = result.get(); LMSContent content = result.get();
var tasks = content.getTasks(); Task task = content.getTask();
var currentTask = tasks.stream().findFirst();
currentTask.ifPresent(task -> task.setData(requestUpdateTask.getData())); if (task != null) {
lmsContentRepository.save(content); updateTaskFromData(task, requestUpdateTask.getData());
lmsContentRepository.save(content);
}
} }
//TODO обработка exception - failed / success //TODO обработка exception - failed / success
return new ResultResponse("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());
}
} }

View File

@ -8,7 +8,9 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class TaskData { public class TaskData {
String name;
String description; String description;
String initScript; String initScript;
String verificationScript; String verificationScript;
String deleteScript;
} }

View File

@ -8,29 +8,29 @@
<changeSet id="2025-12-01" author="dzyk"> <changeSet id="2025-12-01" author="dzyk">
<createTable tableName="task_queue"> <createTable tableName="history">
<column autoIncrement="true" name="id" type="BIGINT"> <column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_TASK_QUEUE"/> <constraints nullable="false" primaryKey="true" primaryKeyName="PK_HISTORY"/>
</column> </column>
<column name="task_id" type="bigint"/> <column name="deployment_id" type="uuid">
<constraints nullable="false"/>
</column>
<column name="content" type="jsonb"/> <column name="content_uuid" type="uuid">
<constraints nullable="false"/>
</column>
<column name="status" type="varchar(20)"/> <column name="created" type="timestamp">
<constraints nullable="false"/>
</column>
<column name="finished" type="boolean"/> <column name="message" type="varchar(2048)">
<constraints nullable="false"/>
</column>
</createTable> </createTable>
<addForeignKeyConstraint
baseTableName="task_queue"
baseColumnNames="task_id"
constraintName="fk_task_queue_task"
referencedTableName="task"
referencedColumnNames="id"
onDelete="NO ACTION"/>
</changeSet> </changeSet>
</databaseChangeLog> </databaseChangeLog>

View File

@ -14,13 +14,21 @@
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_LMS_CONTENT"/> <constraints nullable="false" primaryKey="true" primaryKeyName="PK_LMS_CONTENT"/>
</column> </column>
<column name="deployment_id" type="uuid"/> <column name="deployment_id" type="uuid">
<constraints nullable="false"/>
</column>
<column name="content_key" type="bigint"/> <column name="content_uuid" type="uuid">
<constraints nullable="false"/>
</column>
<column name="content_uuid" type="uuid"/> <column name="created" type="timestamp">
<constraints nullable="false"/>
</column>
<column name="task_id" type="bigint"/> <column name="task_id" type="bigint">
<constraints nullable="false"/>
</column>
</createTable> </createTable>
@ -32,5 +40,11 @@
referencedColumnNames="id" referencedColumnNames="id"
onDelete="NO ACTION"/> onDelete="NO ACTION"/>
<createIndex
tableName="lms_content"
indexName="idx_content_uuid">
<column name="content_uuid"/>
</createIndex>
</changeSet> </changeSet>
</databaseChangeLog> </databaseChangeLog>

View File

@ -14,9 +14,19 @@
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_TASK"/> <constraints nullable="false" primaryKey="true" primaryKeyName="PK_TASK"/>
</column> </column>
<column name="task_type" type="varchar(20)"/> <column name="name" type="varchar(250)">
<constraints nullable="false"/>
</column>
<column name="data" type="jsonb"/> <column name="description" type="text">
<constraints nullable="false"/>
</column>
<column name="init_script" type="text"/>
<column name="verification_script" type="text"/>
<column name="delete_script" type="text"/>
</createTable> </createTable>

View File

@ -10,6 +10,6 @@
<include file="/db/changelog/1.0.0/2025-12-add_task.xml"/> <include file="/db/changelog/1.0.0/2025-12-add_task.xml"/>
<include file="/db/changelog/1.0.0/2025-12-add_lms_content.xml"/> <include file="/db/changelog/1.0.0/2025-12-add_lms_content.xml"/>
<include file="/db/changelog/1.0.0/2025-12-add_task_queue.xml"/> <include file="/db/changelog/1.0.0/2025-12-add_history.xml"/>
</databaseChangeLog> </databaseChangeLog>