server i18n: move inline HTML to templates.go, localize all handler strings
- Admin & user dashboard HTML moved from handlers to templates.go with i18n.T() - SafeVaultPath applied in sync_apply.go (note/file create/update, blob restore) - DeleteNode/RenameNode/MoveNode fixed: correct activity type / entity variant - Added TypeNoteDeleted, TypeNodeDeleted, TypeFolderMoved activity constants - Added locale() helper on Server struct, removed hardcoded 'ru' in handlers - Password policy loosened: 8-256 chars, any characters, machine-readable error codes - check-i18n.sh: Go Cyrillic = FAIL with explicit exception list, Go locale key consistency check
This commit is contained in:
@@ -49,6 +49,37 @@ func (a *App) CreateNode(parentID, nodeType, title, section string) (*NodeDTO, e
|
||||
}
|
||||
|
||||
func (a *App) DeleteNode(id string) error {
|
||||
n, err := a.nodes.GetActive(id)
|
||||
if err != nil {
|
||||
return a.nodes.SoftDelete(id)
|
||||
}
|
||||
pid := ""
|
||||
if n.ParentID != nil {
|
||||
pid = *n.ParentID
|
||||
}
|
||||
var entity string
|
||||
var targetType string
|
||||
var evType string
|
||||
switch n.Type {
|
||||
case nodes.TypeNote:
|
||||
entity = syncsvc.EntityNote
|
||||
targetType = activity.TargetNote
|
||||
evType = activity.TypeNoteDeleted
|
||||
case nodes.TypeFolder:
|
||||
entity = syncsvc.EntityFolder
|
||||
targetType = activity.TargetFolder
|
||||
evType = activity.TypeFolderDeleted
|
||||
case nodes.TypeFile:
|
||||
entity = syncsvc.EntityFile
|
||||
targetType = activity.TargetFile
|
||||
evType = activity.TypeFileDeleted
|
||||
default:
|
||||
entity = syncsvc.EntityNode
|
||||
targetType = activity.TargetNode
|
||||
evType = activity.TypeNodeDeleted
|
||||
}
|
||||
_ = a.activity.Record(pid, targetType, id, "", evType, n.Title, "")
|
||||
_ = a.sync.RecordOp(entity, id, syncsvc.OpDelete, nil)
|
||||
return a.nodes.SoftDelete(id)
|
||||
}
|
||||
|
||||
@@ -65,17 +96,28 @@ func (a *App) RenameNode(nodeID, newTitle string) error {
|
||||
if n.ParentID != nil {
|
||||
pid = *n.ParentID
|
||||
}
|
||||
evType := activity.TypeFileRenamed
|
||||
targetType := activity.TargetFile
|
||||
if n.Type == nodes.TypeFolder {
|
||||
var evType string
|
||||
var targetType string
|
||||
var syncEntity string
|
||||
switch n.Type {
|
||||
case nodes.TypeNote:
|
||||
evType = activity.TypeNoteUpdated
|
||||
targetType = activity.TargetNote
|
||||
syncEntity = syncsvc.EntityNote
|
||||
case nodes.TypeFile:
|
||||
evType = activity.TypeFileRenamed
|
||||
targetType = activity.TargetFile
|
||||
syncEntity = syncsvc.EntityFile
|
||||
case nodes.TypeFolder:
|
||||
evType = activity.TypeFolderRenamed
|
||||
targetType = activity.TargetFolder
|
||||
syncEntity = syncsvc.EntityFolder
|
||||
default:
|
||||
evType = activity.TypeNodeUpdated
|
||||
targetType = activity.TargetNode
|
||||
syncEntity = syncsvc.EntityNode
|
||||
}
|
||||
_ = 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),
|
||||
@@ -108,8 +150,29 @@ func (a *App) MoveNode(nodeID, newParentID string) error {
|
||||
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{}{
|
||||
var targetType string
|
||||
var evType string
|
||||
var syncEntity string
|
||||
switch node.Type {
|
||||
case nodes.TypeNote:
|
||||
targetType = activity.TargetNote
|
||||
evType = activity.TypeNoteUpdated
|
||||
syncEntity = syncsvc.EntityNote
|
||||
case nodes.TypeFile:
|
||||
targetType = activity.TargetFile
|
||||
evType = activity.TypeFileMoved
|
||||
syncEntity = syncsvc.EntityFile
|
||||
case nodes.TypeFolder:
|
||||
targetType = activity.TargetFolder
|
||||
evType = activity.TypeFolderMoved
|
||||
syncEntity = syncsvc.EntityFolder
|
||||
default:
|
||||
targetType = activity.TargetNode
|
||||
evType = activity.TypeNodeUpdated
|
||||
syncEntity = syncsvc.EntityNode
|
||||
}
|
||||
_ = a.activity.Record(pid, targetType, nodeID, "", evType, node.Title, `{"to":"`+newParentID+`"}`)
|
||||
_ = a.sync.RecordOp(syncEntity, nodeID, syncsvc.OpMove, map[string]interface{}{
|
||||
"parent_id": newParentID,
|
||||
"updated_at": time.Now().UTC().Format(time.RFC3339),
|
||||
})
|
||||
|
||||
@@ -193,14 +193,26 @@ func (a *App) applyRemoteNoteCreate(op syncsvc.Op) error {
|
||||
}
|
||||
}
|
||||
|
||||
dest := filepath.Join(a.vault, payload.Path)
|
||||
var dest string
|
||||
if payload.Path == "" {
|
||||
filename := payload.Filename
|
||||
if filename == "" {
|
||||
filename = payload.NodeID[:8] + ".md"
|
||||
} else {
|
||||
cleanFilename, err := syncsvc.SafeVaultPath(a.vault, filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unsafe path in %s: %w", op.EntityType, err)
|
||||
}
|
||||
filename = cleanFilename
|
||||
}
|
||||
dest = filepath.Join(a.vault, "spaces", filename)
|
||||
payload.Path, _ = filepath.Rel(a.vault, dest)
|
||||
} else {
|
||||
cleanPath, err := syncsvc.SafeVaultPath(a.vault, payload.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unsafe path in %s: %w", op.EntityType, err)
|
||||
}
|
||||
dest = filepath.Join(a.vault, cleanPath)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0o750); err != nil {
|
||||
return err
|
||||
@@ -354,7 +366,11 @@ func (a *App) applyRemoteFileCreate(op syncsvc.Op) error {
|
||||
}
|
||||
}
|
||||
|
||||
dest := filepath.Join(a.vault, payload.Path)
|
||||
cleanPath, pathErr := syncsvc.SafeVaultPath(a.vault, payload.Path)
|
||||
if pathErr != nil {
|
||||
return fmt.Errorf("unsafe path in file: %w", pathErr)
|
||||
}
|
||||
dest := filepath.Join(a.vault, cleanPath)
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0o750); err == nil {
|
||||
input, rErr := os.ReadFile(blobPath)
|
||||
if rErr == nil {
|
||||
|
||||
Reference in New Issue
Block a user