verstak/docs/03_Data_Model_Storage.md

6.3 KiB
Raw Blame History

Верстак — модель данных и хранилище

1. Vault

Пример структуры:

~/VerstakVault/
  .verstak/
    index.db
    config.yml
    device_token.json
    blobs/
    plugins/
    trash/
    history/
    originals/
    thumbnails/

  spaces/
    clients/
      romashka/
        case.yml
        overview.md
        notes/
          nginx.md
          mysql-cleanup.md
          access.secret.md.enc
        documents/
          dogovor-2026.docx
          sluzhebka.pdf
        screenshots/
          error-form.png
        scripts/
          backup-site.sh
          cleanup.sql
        actions/
          open-admin.yml
        worklog/
          2026-05.md

2. SQLite schema MVP

nodes

CREATE TABLE nodes (
    id TEXT PRIMARY KEY,
    parent_id TEXT NULL REFERENCES nodes(id),
    type TEXT NOT NULL,
    title TEXT NOT NULL,
    slug TEXT NOT NULL,
    path TEXT NULL,
    section TEXT NULL,
    template_id TEXT NOT NULL DEFAULT '',
    fs_path TEXT NOT NULL DEFAULT '',
    archived INTEGER NOT NULL DEFAULT 0,
    sort_order INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    deleted_at TEXT NULL,
    revision INTEGER NOT NULL DEFAULT 1,
    device_id TEXT NULL
);

node_meta

CREATE TABLE node_meta (
    node_id TEXT NOT NULL REFERENCES nodes(id),
    key TEXT NOT NULL,
    value TEXT NOT NULL,
    PRIMARY KEY (node_id, key)
);

files

CREATE TABLE files (
    id TEXT PRIMARY KEY,
    node_id TEXT NOT NULL REFERENCES nodes(id),
    filename TEXT NOT NULL,
    path TEXT NOT NULL,
    storage_mode TEXT NOT NULL, -- vault | external
    size INTEGER NOT NULL DEFAULT 0,
    sha256 TEXT NULL,
    mime TEXT NULL,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    last_seen_at TEXT NULL,
    missing INTEGER NOT NULL DEFAULT 0
);

notes

CREATE TABLE notes (
    node_id TEXT PRIMARY KEY REFERENCES nodes(id),
    file_id TEXT NOT NULL REFERENCES files(id),
    format TEXT NOT NULL DEFAULT 'markdown',
    original_format TEXT NULL,
    encrypted INTEGER NOT NULL DEFAULT 0
);

actions

CREATE TABLE actions (
    id TEXT PRIMARY KEY,
    node_id TEXT NOT NULL REFERENCES nodes(id),
    title TEXT NOT NULL,
    kind TEXT NOT NULL,
    command TEXT NULL,
    args_json TEXT NULL,
    working_dir TEXT NULL,
    url TEXT NULL,
    confirm_required INTEGER NOT NULL DEFAULT 0,
    capture_output INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

worklog_entries

CREATE TABLE worklog_entries (
    id TEXT PRIMARY KEY,
    node_id TEXT NOT NULL REFERENCES nodes(id),
    started_at TEXT NULL,
    ended_at TEXT NULL,
    date TEXT NOT NULL,
    minutes INTEGER NULL,
    approximate INTEGER NOT NULL DEFAULT 1,
    billable INTEGER NOT NULL DEFAULT 0,
    summary TEXT NOT NULL,
    details TEXT NULL,
    source TEXT NOT NULL DEFAULT 'unknown',
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

activity_events

CREATE TABLE activity_events (
    id TEXT PRIMARY KEY,
    node_id TEXT NOT NULL,
    parent_id TEXT,
    event_type TEXT NOT NULL,
    target_type TEXT,
    target_id TEXT,
    target_path TEXT,
    title TEXT NOT NULL DEFAULT '',
    metadata TEXT NOT NULL DEFAULT '{}',
    created_at TEXT NOT NULL
);

worklog_entry_events

CREATE TABLE worklog_entry_events (
    entry_id TEXT NOT NULL REFERENCES worklog_entries(id) ON DELETE CASCADE,
    event_id TEXT NOT NULL,
    PRIMARY KEY (entry_id, event_id)
);

search_index

CREATE VIRTUAL TABLE search_index USING fts5(
    node_id UNINDEXED,
    title,
    content,
    path,
    tags,
    type
);

sync_ops

CREATE TABLE sync_ops (
    id TEXT PRIMARY KEY,
    op_id TEXT NOT NULL UNIQUE,
    device_id TEXT NOT NULL,
    entity_type TEXT NOT NULL,
    entity_id TEXT NOT NULL,
    op_type TEXT NOT NULL,
    payload_json TEXT NOT NULL,
    created_at TEXT NOT NULL,
    pushed_at TEXT NULL,
    applied_at TEXT NULL
);

CREATE TABLE sync_state (
    device_id TEXT PRIMARY KEY,
    server_url TEXT NOT NULL DEFAULT '',
    api_key TEXT NOT NULL DEFAULT '',
    last_push_rev INTEGER NOT NULL DEFAULT 0,
    last_pull_rev INTEGER NOT NULL DEFAULT 0,
    last_sync_at TEXT,
    last_pull_seq INTEGER NOT NULL DEFAULT 0
);

3. Правила хранения

Заметки

  • physical .md file;
  • metadata in SQLite;
  • backup old version before overwrite.

Документы

  • physical file;
  • open with system app;
  • metadata in SQLite.

Secret notes

  • encrypted file, e.g. access.secret.md.enc;
  • no FTS indexing;
  • no logs with plaintext;
  • master password later.

Удаление

  • soft delete node;
  • file to .verstak/trash;
  • sync tombstone;
  • physical cleanup only after retention.

4. Sync operations

Каждое изменение должно создавать operation:

  • node_create;
  • node_update;
  • node_move;
  • node_delete;
  • file_add;
  • file_update;
  • file_delete;
  • note_update;
  • action_update;
  • worklog_create;
  • worklog_update.

Даже если sync ещё не реализован, operation log лучше заложить рано.

5. Индексация

Индексировать:

  • node titles;
  • note content;
  • filenames;
  • paths;
  • worklog summaries/details;
  • action titles;
  • links.

Не индексировать:

  • secret notes;
  • private keys;
  • token-like values;
  • binary content in MVP.

6. Расширяемость через плагины

Базовая схема фиксирована и поддерживает плагины:

  • Новые типы нод регистрируются плагинами через Lua API (verstak.node.register_type()) — схема таблицы nodes не меняется, type принимает любое строковое значение.
  • Мета-поля (node_meta) хранят произвольные key-value пары, зарегистрированные плагинами.
  • Плагины могут создавать собственные таблицы через SQL-миграции в своей директории .verstak/plugins/<name>/migrations/.
  • device_id на уровне nodes позволяет плагинам синхронизировать свои данные через sync_ops.