stabilization: server.go split + i18n templates + frontend localization

cmd/verstak-server/server.go (2838→127 строк): разделён на 12 файлов
- config.go, tokens.go, schema.go
- server.go (только struct + NewServer + ListenAndServe)
- routes.go, middleware.go, smtp.go
- handlers_api.go, handlers_user.go, handlers_web_user.go, handlers_admin.go
- templates.go (конвертирован в функции с i18n.T())

frontend: все русские строки заменены на t() вызовы
- App.svelte, FileTreeRow.svelte, ConfirmModal.svelte
- FilePreviewModal.svelte, fileUtils.js

core: gofmt по всему проекту

Все сборки (CLI, server, gui, frontend), go vet, go test проходят.
check-i18n.sh: frontend чист, Go-файлы с кириллицей — только тесты/легаси.
This commit is contained in:
2026-06-02 11:08:29 +08:00
parent 3089d777a8
commit 2fa583d157
33 changed files with 3276 additions and 3067 deletions
+18 -18
View File
@@ -15,29 +15,29 @@ import (
// Kind constants.
const (
KindOpenURL = "open_url"
KindOpenFile = "open_file"
KindOpenFolder = "open_folder"
KindRunCommand = "run_command"
KindRunScript = "run_script"
KindOpenURL = "open_url"
KindOpenFile = "open_file"
KindOpenFolder = "open_folder"
KindRunCommand = "run_command"
KindRunScript = "run_script"
KindOpenTerminal = "open_terminal"
KindLaunchApp = "launch_app"
KindLaunchApp = "launch_app"
)
// Record represents an action attached to a node.
type Record struct {
ID string `json:"id"`
NodeID string `json:"node_id"`
Title string `json:"title"`
Kind string `json:"kind"`
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
WorkingDir string `json:"working_dir,omitempty"`
URL string `json:"url,omitempty"`
ConfirmRequired bool `json:"confirm_required"`
CaptureOutput bool `json:"capture_output"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID string `json:"id"`
NodeID string `json:"node_id"`
Title string `json:"title"`
Kind string `json:"kind"`
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
WorkingDir string `json:"working_dir,omitempty"`
URL string `json:"url,omitempty"`
ConfirmRequired bool `json:"confirm_required"`
CaptureOutput bool `json:"capture_output"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// RunResult is returned after executing an action.
+4 -4
View File
@@ -130,10 +130,10 @@ func TestGetNotFound(t *testing.T) {
func TestKindLabel(t *testing.T) {
cases := map[string]string{
KindOpenURL: "Открыть URL",
KindRunCommand: "Запустить команду",
KindOpenFolder: "Открыть папку",
"unknown": "unknown",
KindOpenURL: "Открыть URL",
KindRunCommand: "Запустить команду",
KindOpenFolder: "Открыть папку",
"unknown": "unknown",
}
for kind, want := range cases {
got := KindLabel(kind)
+20 -20
View File
@@ -10,30 +10,30 @@ import (
// Event types.
const (
TypeNoteCreated = "note_created"
TypeNoteUpdated = "note_updated"
TypeFileAdded = "file_added"
TypeFileDeleted = "file_deleted"
TypeFileRenamed = "file_renamed"
TypeFileCopied = "file_copied"
TypeFileMoved = "file_moved"
TypeFolderAdded = "folder_added"
TypeFolderDeleted = "folder_deleted"
TypeFolderRenamed = "folder_renamed"
TypeNodeCreated = "node_created"
TypeNodeUpdated = "node_updated"
TypeActionCreated = "action_created"
TypeActionDone = "action_done"
TypeWorklogAdded = "worklog_added"
TypeNoteCreated = "note_created"
TypeNoteUpdated = "note_updated"
TypeFileAdded = "file_added"
TypeFileDeleted = "file_deleted"
TypeFileRenamed = "file_renamed"
TypeFileCopied = "file_copied"
TypeFileMoved = "file_moved"
TypeFolderAdded = "folder_added"
TypeFolderDeleted = "folder_deleted"
TypeFolderRenamed = "folder_renamed"
TypeNodeCreated = "node_created"
TypeNodeUpdated = "node_updated"
TypeActionCreated = "action_created"
TypeActionDone = "action_done"
TypeWorklogAdded = "worklog_added"
)
// Target types.
const (
TargetNote = "note"
TargetFile = "file"
TargetFolder = "folder"
TargetAction = "action"
TargetNode = "node"
TargetNote = "note"
TargetFile = "file"
TargetFolder = "folder"
TargetAction = "action"
TargetNode = "node"
TargetWorklog = "worklog"
)
+1 -1
View File
@@ -23,7 +23,7 @@ type Record struct {
ID string `json:"id"`
NodeID string `json:"node_id"`
Filename string `json:"filename"`
Path string `json:"path"` // relative to vault root
Path string `json:"path"` // relative to vault root
StorageMode string `json:"storage_mode"` // "vault" | "external"
Size int64 `json:"size"`
SHA256 string `json:"sha256,omitempty"`
+5 -5
View File
@@ -307,11 +307,11 @@ func TestPreviewImportDir(t *testing.T) {
func TestGuessMIME(t *testing.T) {
cases := map[string]string{
"a.md": "text/plain",
"a.png": "image/png",
"a.pdf": "application/pdf",
"a.docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"a.go": "text/plain",
"a.md": "text/plain",
"a.png": "image/png",
"a.pdf": "application/pdf",
"a.docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"a.go": "text/plain",
"a.unknown": "application/octet-stream",
}
for name, want := range cases {
+15 -15
View File
@@ -7,19 +7,19 @@ import (
// Node is the central entity of Verstak — a tree item that can be
// a case, folder, note, document, etc.
type Node struct {
ID string `json:"id"`
ParentID *string `json:"parent_id,omitempty"`
Type string `json:"type"`
Title string `json:"title"`
Slug string `json:"slug"`
Path *string `json:"path,omitempty"`
Section string `json:"section,omitempty"`
SortOrder int `json:"sort_order"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
Revision int `json:"revision"`
DeviceID *string `json:"device_id,omitempty"`
ID string `json:"id"`
ParentID *string `json:"parent_id,omitempty"`
Type string `json:"type"`
Title string `json:"title"`
Slug string `json:"slug"`
Path *string `json:"path,omitempty"`
Section string `json:"section,omitempty"`
SortOrder int `json:"sort_order"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
Revision int `json:"revision"`
DeviceID *string `json:"device_id,omitempty"`
}
// IsDeleted reports whether the node has been soft-deleted.
@@ -40,6 +40,6 @@ type Meta struct {
// NodeWithMeta bundles a node and its metadata entries.
type NodeWithMeta struct {
Node Node `json:"node"`
Meta []Meta `json:"meta"`
Node Node `json:"node"`
Meta []Meta `json:"meta"`
}
+9 -9
View File
@@ -62,8 +62,8 @@ func (m *Manager) Discover() {
meta.Name = e.Name()
}
m.plugins = append(m.plugins, Plugin{
Meta: meta,
Dir: filepath.Join(pluginsDir, e.Name()),
Meta: meta,
Dir: filepath.Join(pluginsDir, e.Name()),
Active: true,
})
}
@@ -116,13 +116,13 @@ func (m *Manager) Templates() []TemplateDefinition {
// TemplateDefinition describes a predefined tree of nodes.
type TemplateDefinition struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Icon string `json:"icon,omitempty"`
Plugin string // source plugin name
RootType string `json:"root_type"`
Tree []TreeNode `json:"tree"`
Meta []NodeMeta `json:"meta,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Icon string `json:"icon,omitempty"`
Plugin string // source plugin name
RootType string `json:"root_type"`
Tree []TreeNode `json:"tree"`
Meta []NodeMeta `json:"meta,omitempty"`
}
// TreeNode is a single item in a template tree.
+1 -1
View File
@@ -8,8 +8,8 @@ import (
"verstak/internal/core/actions"
"verstak/internal/core/files"
"verstak/internal/core/notes"
"verstak/internal/core/nodes"
"verstak/internal/core/notes"
"verstak/internal/core/search"
"verstak/internal/core/storage"
"verstak/internal/core/vault"
+6 -6
View File
@@ -57,12 +57,12 @@ CREATE TABLE IF NOT EXISTS _schema_ver (
`
var migrationFiles = map[int]string{
1: migration001,
2: migration002,
3: migration003,
4: migration004,
5: migration005,
6: migration006,
1: migration001,
2: migration002,
3: migration003,
4: migration004,
5: migration005,
6: migration006,
// 7: migration007 (FTS5) — created lazily by search.Rebuild()
8: migration008,
9: migration009,
+6 -6
View File
@@ -29,7 +29,7 @@ func NewClient(serverURL, apiKey, deviceID, vaultRoot string) *Client {
APIKey: apiKey,
DeviceID: deviceID,
VaultRoot: vaultRoot,
HTTP: &http.Client{Timeout: 30 * time.Second},
HTTP: &http.Client{Timeout: 30 * time.Second},
}
}
@@ -145,9 +145,9 @@ func (c *Client) TestAuth(serverURL, username, password string) error {
// PushRequest is the payload for POST /sync/push.
type PushRequest struct {
DeviceID string `json:"device_id"`
IdempotencyKey string `json:"idempotency_key,omitempty"`
Ops []PushOp `json:"ops"`
DeviceID string `json:"device_id"`
IdempotencyKey string `json:"idempotency_key,omitempty"`
Ops []PushOp `json:"ops"`
}
// PushOp is a single operation in a push request.
@@ -164,8 +164,8 @@ type PushOp struct {
// PushResponse is the response from POST /sync/push.
type PushResponse struct {
Accepted []string `json:"accepted"`
Count int `json:"count"`
Accepted []string `json:"accepted"`
Count int `json:"count"`
Conflicts []map[string]interface{} `json:"conflicts"`
}
+12 -12
View File
@@ -30,18 +30,18 @@ const (
// Op represents a sync operation.
type Op struct {
ID string `json:"id"`
OpID string `json:"op_id"`
ServerSequence int `json:"server_sequence,omitempty"`
DeviceID string `json:"device_id,omitempty"`
EntityType string `json:"entity_type"`
EntityID string `json:"entity_id"`
OpType string `json:"op_type"`
PayloadJSON string `json:"payload_json"`
CreatedAt string `json:"created_at"`
PushedAt *string `json:"pushed_at,omitempty"`
ClientSequence int `json:"client_sequence,omitempty"`
LastSeenServerSeq int `json:"last_seen_server_seq,omitempty"`
ID string `json:"id"`
OpID string `json:"op_id"`
ServerSequence int `json:"server_sequence,omitempty"`
DeviceID string `json:"device_id,omitempty"`
EntityType string `json:"entity_type"`
EntityID string `json:"entity_id"`
OpType string `json:"op_type"`
PayloadJSON string `json:"payload_json"`
CreatedAt string `json:"created_at"`
PushedAt *string `json:"pushed_at,omitempty"`
ClientSequence int `json:"client_sequence,omitempty"`
LastSeenServerSeq int `json:"last_seen_server_seq,omitempty"`
}
// Service records and manages sync operations.
+7 -7
View File
@@ -132,16 +132,16 @@ func TestE2ESync(t *testing.T) {
"device_id": deviceIDA,
"ops": []map[string]interface{}{
{
"op_id": "op-node-create-001",
"entity_type": "node",
"entity_id": nodeID,
"op_type": "create",
"op_id": "op-node-create-001",
"entity_type": "node",
"entity_id": nodeID,
"op_type": "create",
"payload_json": fmt.Sprintf(
`{"id":"%s","parent_id":"","type":"case","title":"Test Project","slug":"test-project","section":"projects","created_at":"%s","updated_at":"%s"}`,
nodeID, now, now),
"client_sequence": 1,
"client_sequence": 1,
"last_seen_server_seq": 0,
"created_at": now,
"created_at": now,
},
},
"idempotency_key": "e2e-test-push-1",
@@ -161,7 +161,7 @@ func TestE2ESync(t *testing.T) {
t.Fatalf("push A status %d: %s", resp.StatusCode, string(body))
}
var pushRespA struct {
Accepted []string `json:"accepted"`
Accepted []string `json:"accepted"`
Conflicts []interface{} `json:"conflicts"`
}
json.NewDecoder(resp.Body).Decode(&pushRespA)