package main import ( "fmt" "time" "verstak/internal/core/activity" "verstak/internal/core/nodes" syncsvc "verstak/internal/core/sync" ) func (a *App) ListNodesBySection(section string) ([]NodeDTO, error) { list, err := a.nodes.ListRoots(false, section) if err != nil { return nil, err } return toNodeDTOs(list), nil } func (a *App) ListChildren(parentID string) ([]NodeDTO, error) { list, err := a.nodes.ListChildren(parentID, false) if err != nil { return nil, err } return toNodeDTOs(list), nil } func (a *App) GetNodeDetail(nodeID string) (*NodeDTO, error) { n, err := a.nodes.GetActive(nodeID) if err != nil { return nil, err } dto := toNodeDTO(n) return &dto, nil } func (a *App) CreateNode(parentID, nodeType, title, section string) (*NodeDTO, error) { if section == "today" || section == "inbox" { return nil, fmt.Errorf("cannot create node with section %q", section) } n, err := a.nodes.Create(parentID, nodeType, title, section) if err != nil { return nil, err } _ = a.activity.Record(n.ID, activity.TargetNode, n.ID, "", activity.TypeNodeCreated, title, "") _ = a.sync.RecordOp(syncsvc.EntityNode, n.ID, syncsvc.OpCreate, nodePayload(n)) dto := toNodeDTO(n) return &dto, nil } func (a *App) DeleteNode(id string) error { return a.nodes.SoftDelete(id) } func (a *App) RenameNode(nodeID, newTitle string) error { n, err := a.nodes.GetActive(nodeID) if err != nil { return err } oldTitle := n.Title if err := a.nodes.UpdateTitle(nodeID, newTitle); err != nil { return err } pid := "" if n.ParentID != nil { pid = *n.ParentID } evType := activity.TypeFileRenamed targetType := activity.TargetFile if n.Type == nodes.TypeFolder { evType = activity.TypeFolderRenamed 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]interface{}{ "title": newTitle, "updated_at": time.Now().UTC().Format(time.RFC3339), }) return nil } func (a *App) MoveNode(nodeID, newParentID string) error { destChildren, err := a.nodes.ListChildren(newParentID, false) if err != nil { return err } node, err := a.nodes.GetActive(nodeID) if err != nil { return err } for i := range destChildren { if destChildren[i].Title == node.Title { newName := a.files.UniqueTitleCopy(newParentID, node.Title) if err := a.nodes.UpdateTitle(nodeID, newName); err != nil { return err } break } } if err := a.nodes.Move(nodeID, newParentID, 0); err != nil { return err } pid := "" if node.ParentID != nil { 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]interface{}{ "parent_id": newParentID, "updated_at": time.Now().UTC().Format(time.RFC3339), }) return nil }