upd store
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -9,7 +8,6 @@ import (
|
||||
"git.gm6.ru/icewind/seadoc/store"
|
||||
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// VersionService — слой логики версионирования, использующий интерфейс VersionStorage.
|
||||
@@ -20,8 +18,8 @@ type VersionService struct {
|
||||
}
|
||||
|
||||
// NewVersionService создаёт новый сервис
|
||||
func NewVersionService(storage store.IVersionStorage) *VersionService {
|
||||
return &VersionService{storage: storage, snapshotInterval: 0}
|
||||
func NewVersionService(storage store.IVersionStorage, snapshotInterval int) *VersionService {
|
||||
return &VersionService{storage: storage, snapshotInterval: snapshotInterval}
|
||||
}
|
||||
|
||||
// SetSnapshotInterval задаёт через сколько версий сохранять snapshot (если >0)
|
||||
@@ -29,111 +27,94 @@ func (s *VersionService) SetSnapshotInterval(n int) {
|
||||
s.snapshotInterval = n
|
||||
}
|
||||
|
||||
func (s *VersionService) CreateDocument(name string) (*models.Document, error) {
|
||||
doc := models.Document{
|
||||
ID: uuid.New(),
|
||||
Name: name,
|
||||
}
|
||||
if err := s.storage.CreateDocument(&doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &doc, nil
|
||||
}
|
||||
|
||||
// SaveNewVersion создаёт новую версию документа (merge-patch или snapshot)
|
||||
func (s *VersionService) SaveNewVersion(docID uuid.UUID, newJSON []byte) (*models.Version, error) {
|
||||
lastVer, err := s.storage.GetLatestVersion(docID)
|
||||
|
||||
var patchBytes []byte
|
||||
isSnapshot := false
|
||||
var parentID *uuid.UUID
|
||||
|
||||
if err != nil {
|
||||
// первая версия — snapshot
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
isSnapshot = true
|
||||
parentID = nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Если нет предыдущих версий — первая версия всегда snapshot
|
||||
if lastVer == nil {
|
||||
newVer := models.Version{
|
||||
ID: uuid.New(),
|
||||
DocumentID: docID,
|
||||
ParentID: nil,
|
||||
IsSnapshot: true,
|
||||
Snapshot: newJSON,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if err := s.storage.CreateVersion(&newVer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// есть предыдущая версия — создаём merge-patch
|
||||
parentID = &lastVer.ID
|
||||
_ = s.storage.UpdateLatestVersion(docID, newVer.ID)
|
||||
return &newVer, nil
|
||||
}
|
||||
|
||||
var oldJSON []byte
|
||||
if lastVer.IsSnapshot {
|
||||
oldJSON = lastVer.Snapshot
|
||||
} else {
|
||||
oldJSON, err = s.ReconstructVersion(lastVer.ID)
|
||||
if err != nil {
|
||||
// Если включён интервал снапшота — проверяем глубину до создания патча
|
||||
if s.snapshotInterval > 0 {
|
||||
depth, err := s.storage.CountSinceLastSnapshot(docID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// если глубина достигла порога, сохраняем snapshot ВМЕСТО патча
|
||||
if depth >= s.snapshotInterval {
|
||||
snapVer := models.Version{
|
||||
ID: uuid.New(),
|
||||
DocumentID: docID,
|
||||
ParentID: &lastVer.ID,
|
||||
IsSnapshot: true,
|
||||
Snapshot: newJSON,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if err := s.storage.CreateVersion(&snapVer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = s.storage.UpdateLatestVersion(docID, snapVer.ID)
|
||||
return &snapVer, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Создаём JSON Merge Patch (RFC 7386)
|
||||
// CreateMergePatch возвращает []byte с содержимым merge-patch
|
||||
patchBytes, err = jsonpatch.CreateMergePatch(oldJSON, newJSON)
|
||||
// Иначе: создаём merge-patch относительно старой версии
|
||||
var old []byte
|
||||
if lastVer.IsSnapshot {
|
||||
old = lastVer.Snapshot
|
||||
} else {
|
||||
old, err = s.ReconstructVersion(lastVer.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create merge patch: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Если patch пустой (документы равны), можно не сохранять новую версию
|
||||
// В данном примере создаём версию даже если patch пустой — можно изменить поведение
|
||||
patch, err := jsonpatch.CreateMergePatch(old, newJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newVer := models.Version{
|
||||
ID: uuid.New(),
|
||||
DocumentID: docID,
|
||||
ParentID: parentID,
|
||||
IsSnapshot: isSnapshot,
|
||||
Patch: patchBytes,
|
||||
ParentID: &lastVer.ID,
|
||||
IsSnapshot: false,
|
||||
Patch: patch,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if isSnapshot {
|
||||
newVer.Snapshot = newJSON
|
||||
}
|
||||
|
||||
// Сохраняем версию в хранилище
|
||||
if err := s.storage.CreateVersion(&newVer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Обновляем ссылку на последнюю версию документа
|
||||
if err := s.storage.UpdateLatestVersion(docID, newVer.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Опционально: если указан snapshotInterval, проверяем нужно ли создать snapshot
|
||||
if s.snapshotInterval > 0 && !isSnapshot {
|
||||
// считаем количество версий от этого snapshot'а до новой версии
|
||||
// простая реализация: пройти вверх по родителям и посчитать
|
||||
count := 0
|
||||
curr := &newVer
|
||||
for {
|
||||
count++
|
||||
if curr.ParentID == nil {
|
||||
break
|
||||
}
|
||||
parent, err := s.storage.GetParentVersion(curr)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if parent == nil {
|
||||
break
|
||||
}
|
||||
curr = parent
|
||||
if curr.IsSnapshot {
|
||||
break
|
||||
}
|
||||
}
|
||||
if count >= s.snapshotInterval {
|
||||
// делаем snapshot: восстанавливаем полную версию и сохраняем snapshot-версию
|
||||
full, err := s.ReconstructVersion(newVer.ID)
|
||||
if err == nil {
|
||||
snapVer := models.Version{
|
||||
ID: uuid.New(),
|
||||
DocumentID: docID,
|
||||
ParentID: &newVer.ID,
|
||||
IsSnapshot: true,
|
||||
Snapshot: full,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
// сохраняем snapshot-версию и обновляем latest_version
|
||||
_ = s.storage.CreateVersion(&snapVer)
|
||||
_ = s.storage.UpdateLatestVersion(docID, snapVer.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = s.storage.UpdateLatestVersion(docID, newVer.ID)
|
||||
|
||||
return &newVer, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user