# База данных децентрализованного чата (chatgui) ## Архитектура хранения Чат **децентрализованный** — нет центрального сервера. Каждый узел (нода uTun) хранит свою **локальную SQLite БД**. В ней: - **Информация о узлах (и о себе)** (как подключиться: адреса, порты, ключи — всё из NODEINFO). плюс nickname. - и список узлов = список участников. каждый узел имеет parent node (дерево). - **История сообщений** — Данные о других узлах поступают из NODEINFO gossip-протокола uTun и синхронизируются в фоне. Сообщения приходят через `msg_transport` (TCP, локальный IPC). аттачи - загружают и кешируют узлы в соответствии со своими настройками. ## Структура таблиц SQLite ### `local_identity` — наш собственный узел (точнее все мои устройства) между моими устройствами синхронизируется контент - список чатов и сообщения. ```sql CREATE TABLE IF NOT EXISTS nodes ( id INTEGER PRIMARY KEY, node_id INTEGER NOT NULL UNIQUE, // id из utun name TEXT NOT NULL, // ник last_seen INTEGER DEFAULT 0, online_rating INTEGER DEFAULT 0, // рейттинг связности с узлом. чем больше число тем чаще узел онлайн и с ним хорошая связь speed_rating INTEGER DEFAULT 0, // рейтинг скорости обмена с узлом rtt_rating INTEGER DEFAULT 0, // rtt до узла (через промежуточные узлы если есть) connectivity_json TEXT, // как связаться с узлом parent_node_id INTEGER DEFAULT NULL, // каждая нода должна иметь родителя (кроме корневой) main_node_id INTEGER DEFAULT NULL, // если несколько устройств у клиента, id главной ноды created_at TEXT DEFAULT (datetime('now')), ); ``` ### `channels` — чаты (группы узлов) ```sql CREATE TABLE IF NOT EXISTS channels ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, last_msg_at TEXT, ); ``` ### `messages` — список пользователей в чате ```sql CREATE TABLE IF NOT EXISTS channel_members ( channel_id INTEGER, node_id INTEGER, // если несколько устройств то ноды всех стройств ); ``` ### `messages` — история сообщений (для каждой группы создаем новую таблицу) ```sql CREATE TABLE IF NOT EXISTS messages_ch ( id INTEGER PRIMARY KEY AUTOINCREMENT, channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE, author_node_id INTEGER NOT NULL, content TEXT NOT NULL, content_type TEXT DEFAULT 'text/plain', timestamp INTEGER NOT NULL, local_seq INTEGER DEFAULT 0, signature BLOB, is_outgoing INTEGER DEFAULT 0, is_read INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_msg_channel_time ON messages(channel_id, timestamp); CREATE INDEX IF NOT EXISTS idx_msg_author ON messages(author_node_id); CREATE UNIQUE INDEX IF NOT EXISTS idx_msg_dedup ON messages(channel_id, author_node_id, timestamp); ``` **Пояснение:** каждое сообщение привязано к каналу. `author_node_id` — кто автор (из `nodes.node_id`). `timestamp` — unix время в миллисекундах *по часам отправителя* (в децентрализованной системе нет глобальных часов, но для порядка в UI достаточно). `local_seq` — монотонно возрастающий номер в пределах канала для детерминированной сортировки при одинаковых timestamp. **Дедупликация:** сообщение идентифицируется тройкой `(channel_id, author_node_id, timestamp)`. Уникальный индекс `idx_msg_dedup` предотвращает дубликаты при повторной доставке через разные пути. | Поле | Тип | Описание | |---|---|---| | `channel_id` | TEXT | Ссылка на канал | | `author_node_id` | INTEGER | Кто отправил (node_id) | | `content` | TEXT | Текст сообщения | | `content_type` | TEXT | MIME-тип: `text/plain`, в будущем `image/png` и т.д. | | `timestamp` | INTEGER | Unix timestamp (мс) — когда отправлено | | `local_seq` | INTEGER | Локальный порядковый номер в канале | | `signature` | BLOB | Ed25519 подпись (64 байта), NULL если без подписи | | `is_outgoing` | INTEGER | 1 = отправлено нами | | `is_read` | INTEGER | 1 = прочитано (для галочек в UI) |