feat: sync — client ops recording in core services
- internal/core/sync/: Service, Client, Blob packages - RecordOp creates sync_ops entries for all mutations - Client for push/pull/blob HTTP to server - Blob SHA-256 hashing and local storage - Wired into app.go alongside activity recording - Device ID from config or fallback
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"verstak/internal/core/plugins"
|
||||
"verstak/internal/core/search"
|
||||
"verstak/internal/core/storage"
|
||||
syncsvc "verstak/internal/core/sync"
|
||||
"verstak/internal/core/worklog"
|
||||
)
|
||||
|
||||
@@ -37,6 +38,7 @@ type App struct {
|
||||
worklog *worklog.Service
|
||||
search *search.Service
|
||||
plugins *plugins.Manager
|
||||
sync *syncsvc.Service
|
||||
vault string
|
||||
}
|
||||
|
||||
@@ -386,6 +388,7 @@ func (a *App) CreateNode(parentID, nodeType, title, section string) (*NodeDTO, e
|
||||
return nil, err
|
||||
}
|
||||
_ = a.activity.Record(n.ID, activity.TargetNode, n.ID, "", activity.TypeNodeCreated, title, "")
|
||||
_ = a.sync.RecordOp(syncsvc.EntityNode, n.ID, syncsvc.OpCreate, map[string]string{"title": title})
|
||||
dto := toNodeDTO(n)
|
||||
return &dto, nil
|
||||
}
|
||||
@@ -480,6 +483,7 @@ func (a *App) CreateNote(parentID, title string) (*NodeDTO, error) {
|
||||
return nil, err
|
||||
}
|
||||
_ = a.activity.Record(parentID, activity.TargetNote, node.ID, "", activity.TypeNoteCreated, title, "")
|
||||
_ = a.sync.RecordOp(syncsvc.EntityNote, node.ID, syncsvc.OpCreate, map[string]string{"title": title})
|
||||
dto := toNodeDTO(node)
|
||||
return &dto, nil
|
||||
}
|
||||
@@ -501,6 +505,7 @@ func (a *App) SaveNote(noteID, content string) error {
|
||||
pid = *n.ParentID
|
||||
}
|
||||
_ = a.activity.Record(pid, activity.TargetNote, noteID, "", activity.TypeNoteUpdated, n.Title, "")
|
||||
_ = a.sync.RecordOp(syncsvc.EntityNote, noteID, syncsvc.OpUpdate, map[string]string{"title": n.Title})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -573,6 +578,7 @@ func (a *App) AddPathCopy(nodeID, sourcePath string) ([]NodeDTO, error) {
|
||||
}
|
||||
for _, n := range nodes {
|
||||
_ = a.activity.Record(nodeID, activity.TargetFile, n.ID, "", activity.TypeFileAdded, n.Title, `{"source":"`+sourcePath+`"}`)
|
||||
_ = a.sync.RecordOp(syncsvc.EntityFile, n.ID, syncsvc.OpCreate, map[string]string{"title": n.Title})
|
||||
}
|
||||
return toNodeDTOs(nodes), nil
|
||||
}
|
||||
@@ -584,6 +590,7 @@ func (a *App) AddPathLink(nodeID, sourcePath string) ([]NodeDTO, error) {
|
||||
}
|
||||
for _, n := range nodes {
|
||||
_ = a.activity.Record(nodeID, activity.TargetFile, n.ID, "", activity.TypeFileAdded, n.Title, `{"source":"`+sourcePath+`"}`)
|
||||
_ = a.sync.RecordOp(syncsvc.EntityFile, n.ID, syncsvc.OpCreate, map[string]string{"title": n.Title})
|
||||
}
|
||||
return toNodeDTOs(nodes), nil
|
||||
}
|
||||
@@ -602,6 +609,11 @@ func (a *App) DeleteFileOrFolder(nodeID string) error {
|
||||
targetType = activity.TargetFolder
|
||||
}
|
||||
_ = a.activity.Record(pid, targetType, nodeID, "", evType, n.Title, "")
|
||||
syncEntity := syncsvc.EntityFile
|
||||
if n.Type == nodes.TypeFolder {
|
||||
syncEntity = syncsvc.EntityFolder
|
||||
}
|
||||
_ = a.sync.RecordOp(syncEntity, nodeID, syncsvc.OpDelete, nil)
|
||||
}
|
||||
return a.files.DeleteNodeAndChildren(nodeID)
|
||||
}
|
||||
@@ -612,6 +624,7 @@ func (a *App) CreateEmptyFile(parentID, filename string) (*NodeDTO, error) {
|
||||
return nil, err
|
||||
}
|
||||
_ = a.activity.Record(parentID, activity.TargetFile, node.ID, "", activity.TypeFileAdded, filename, "")
|
||||
_ = a.sync.RecordOp(syncsvc.EntityFile, node.ID, syncsvc.OpCreate, map[string]string{"title": filename})
|
||||
dto := toNodeDTO(node)
|
||||
return &dto, nil
|
||||
}
|
||||
@@ -628,6 +641,7 @@ func (a *App) DuplicateNode(nodeID string) (*NodeDTO, error) {
|
||||
pid = *n.ParentID
|
||||
}
|
||||
_ = a.activity.Record(pid, activity.TargetFile, node.ID, "", activity.TypeFileCopied, node.Title, "")
|
||||
_ = a.sync.RecordOp(syncsvc.EntityFile, node.ID, syncsvc.OpCreate, map[string]string{"title": node.Title})
|
||||
dto := toNodeDTO(node)
|
||||
return &dto, nil
|
||||
}
|
||||
@@ -652,6 +666,11 @@ func (a *App) RenameNode(nodeID, newTitle string) error {
|
||||
targetType = activity.TargetFolder
|
||||
}
|
||||
_ = a.activity.Record(pid, targetType, nodeID, "", evType, newTitle, `{"from":"`+oldTitle+`","to":"`+newTitle+`"}`)
|
||||
syncEntity := syncsvc.EntityFile
|
||||
if n.Type == nodes.TypeFolder {
|
||||
syncEntity = syncsvc.EntityFolder
|
||||
}
|
||||
_ = a.sync.RecordOp(syncEntity, nodeID, syncsvc.OpUpdate, map[string]string{"title": newTitle})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -687,6 +706,7 @@ func (a *App) MoveNode(nodeID, newParentID string) error {
|
||||
pid = *node.ParentID
|
||||
}
|
||||
_ = a.activity.Record(pid, activity.TargetFile, nodeID, "", activity.TypeFileMoved, node.Title, `{"to":"`+newParentID+`"}`)
|
||||
_ = a.sync.RecordOp(syncsvc.EntityFile, nodeID, syncsvc.OpMove, map[string]string{"title": node.Title})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -725,6 +745,7 @@ func (a *App) CreateAction(nodeID, kind, title, data string) (*ActionDTO, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = a.sync.RecordOp(syncsvc.EntityAction, rec.ID, syncsvc.OpCreate, map[string]string{"title": rec.Title, "kind": rec.Kind})
|
||||
return &ActionDTO{
|
||||
ID: rec.ID,
|
||||
NodeID: rec.NodeID,
|
||||
@@ -735,6 +756,7 @@ func (a *App) CreateAction(nodeID, kind, title, data string) (*ActionDTO, error)
|
||||
}
|
||||
|
||||
func (a *App) DeleteAction(id string) error {
|
||||
_ = a.sync.RecordOp(syncsvc.EntityAction, id, syncsvc.OpDelete, nil)
|
||||
return a.actions.Delete(id)
|
||||
}
|
||||
|
||||
@@ -774,6 +796,7 @@ func (a *App) CreateWorklog(nodeID, summary string, minutes int) (*WorklogDTO, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = a.sync.RecordOp(syncsvc.EntityWorklog, entry.ID, syncsvc.OpCreate, map[string]string{"summary": summary})
|
||||
mins := 0
|
||||
if entry.Minutes != nil {
|
||||
mins = *entry.Minutes
|
||||
|
||||
@@ -8,12 +8,14 @@ import (
|
||||
|
||||
"verstak/internal/core/actions"
|
||||
"verstak/internal/core/activity"
|
||||
"verstak/internal/core/config"
|
||||
"verstak/internal/core/files"
|
||||
"verstak/internal/core/notes"
|
||||
"verstak/internal/core/nodes"
|
||||
"verstak/internal/core/plugins"
|
||||
"verstak/internal/core/search"
|
||||
"verstak/internal/core/storage"
|
||||
syncsvc "verstak/internal/core/sync"
|
||||
"verstak/internal/core/worklog"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
@@ -53,6 +55,16 @@ func main() {
|
||||
pm := plugins.NewManager(abs)
|
||||
pm.Discover()
|
||||
|
||||
// Sync service — use configured device ID or vault ID as fallback.
|
||||
deviceID := ""
|
||||
if cfg, err := config.Load(abs); err == nil {
|
||||
deviceID = cfg.Sync.DeviceID
|
||||
}
|
||||
if deviceID == "" {
|
||||
deviceID = "gui-" + abs[:8]
|
||||
}
|
||||
syncSvc := syncsvc.NewService(db, deviceID)
|
||||
|
||||
app := &App{
|
||||
db: db,
|
||||
nodes: nodeRepo,
|
||||
@@ -63,6 +75,7 @@ func main() {
|
||||
worklog: worklogSvc,
|
||||
search: searchSvc,
|
||||
plugins: pm,
|
||||
sync: syncSvc,
|
||||
vault: abs,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user