You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

167 lines
7.4 KiB

Архитектура чата (группового)
База данных: SQLite3 (WAL mode), chats.db
================================================================================
Схема БД (7 таблиц)
================================================================================
-- 1. my_nodes — наши собственные ноды (разные устройства / инстансы utun)
CREATE TABLE my_nodes (
node_id INTEGER PRIMARY KEY,
name TEXT NOT NULL, -- human-readable имя устройства
x25519_pubkey BLOB NOT NULL, -- 32 bytes
ed25519_pubkey BLOB, -- 32 bytes (derive из x25519 privkey)
is_current INTEGER DEFAULT 0, -- 1 = текущий инстанс (к которому подключён GUI)
created_at INTEGER DEFAULT (unixepoch()),
updated_at INTEGER DEFAULT (unixepoch())
);
-- 2. nodes — известные удалённые узлы (получаем через msg_transport от utun)
CREATE TABLE nodes (
node_id INTEGER PRIMARY KEY,
name TEXT,
x25519_pubkey BLOB NOT NULL, -- 32 bytes
ed25519_pubkey BLOB, -- 32 bytes (для проверки подписи пира)
is_online INTEGER DEFAULT 0,
last_seen_at INTEGER DEFAULT (unixepoch()),
created_at INTEGER DEFAULT (unixepoch()),
updated_at INTEGER DEFAULT (unixepoch())
);
-- 3. channels — каналы (DM и групповые)
CREATE TABLE channels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id TEXT NOT NULL UNIQUE, -- "dm:nodeA_nodeB" (A<B) или "group:hash"
name TEXT NOT NULL,
owner_node_id INTEGER, -- создатель (NULL для DM)
is_dm INTEGER DEFAULT 0,
last_message TEXT,
last_msg_at INTEGER, -- unix timestamp ms
created_at INTEGER DEFAULT (unixepoch())
);
CREATE UNIQUE INDEX idx_channel_cid ON channels(channel_id);
-- 4. channel_members — участники канала
CREATE TABLE channel_members (
channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,
node_id INTEGER NOT NULL,
joined_at INTEGER DEFAULT (unixepoch()),
PRIMARY KEY (channel_id, node_id)
);
CREATE INDEX idx_cm_node ON channel_members(node_id);
-- 5. messages — сообщения чата
CREATE TABLE messages (
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 CHECK(length(content) <= 4096),
content_type TEXT DEFAULT 'text/plain',
timestamp INTEGER NOT NULL, -- unix milliseconds
signature BLOB CHECK(signature IS NULL OR length(signature) == 64),
is_outgoing INTEGER DEFAULT 0,
is_read INTEGER DEFAULT 0,
created_at INTEGER DEFAULT (unixepoch())
);
CREATE INDEX idx_msg_channel_time ON messages(channel_id, timestamp);
CREATE INDEX idx_msg_author ON messages(author_node_id);
-- дедупликация: один автор не может отправить два сообщения в один ms в один канал
CREATE UNIQUE INDEX idx_msg_dedup ON messages(channel_id, author_node_id, timestamp);
-- 6. reactions — реакции на сообщения
CREATE TABLE reactions (
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
node_id INTEGER NOT NULL,
emoji TEXT NOT NULL, -- unicode codepoint или :shortcode:
signature BLOB, -- Ed25519 (64 bytes)
created_at INTEGER DEFAULT (unixepoch()),
PRIMARY KEY (message_id, node_id, emoji)
);
-- 7. attachments — вложения к сообщениям
CREATE TABLE attachments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
filename TEXT NOT NULL,
content_type TEXT NOT NULL, -- MIME type
size INTEGER NOT NULL, -- bytes
sha256 BLOB NOT NULL, -- 32 bytes
file_path TEXT, -- локальный путь к сохранённому файлу
signature BLOB, -- Ed25519 (64 bytes)
is_upload INTEGER DEFAULT 0, -- 0=скачано, 1=наше
created_at INTEGER DEFAULT (unixepoch())
);
CREATE INDEX idx_att_msg ON attachments(message_id);
================================================================================
Основные запросы
================================================================================
-- Текущая нода
SELECT node_id, name, x25519_pubkey, ed25519_pubkey
FROM my_nodes WHERE is_current = 1;
-- Все свои ноды
SELECT node_id, name, is_current FROM my_nodes ORDER BY created_at;
-- Список каналов с последним сообщением (для текущего пользователя)
SELECT c.channel_id, c.name, c.is_dm, c.last_message, c.last_msg_at
FROM channels c
JOIN channel_members cm ON c.channel_id = cm.channel_id
JOIN my_nodes mn ON cm.node_id = mn.node_id
WHERE mn.is_current = 1
ORDER BY c.last_msg_at DESC;
-- Сообщения канала (последние 50)
SELECT id, author_node_id, content, content_type, timestamp,
signature, is_outgoing, is_read
FROM messages
WHERE channel_id = ?
ORDER BY timestamp DESC
LIMIT 50;
-- Кто в канале
SELECT cm.node_id, n.name
FROM channel_members cm LEFT JOIN nodes n ON cm.node_id = n.node_id
WHERE cm.channel_id = ?;
-- DM между двумя узлами
-- channel_id = "dm:" || min(a,b) || "_" || max(a,b)
-- Реакции сообщения
SELECT node_id, emoji, signature FROM reactions WHERE message_id = ? ORDER BY created_at;
-- Вложения сообщения
SELECT id, filename, content_type, size, sha256, file_path
FROM attachments WHERE message_id = ?;
================================================================================
Примечания
================================================================================
- Адреса и подсети (node_addresses, node_subnets) — зона ответственности utun.
Чат не занимается сетевым уровнем. При необходимости utun отдаёт эту информацию
через control_server (etcpmon протокол).
- Все timestamp: INTEGER (unix epoch). messages.timestamp — миллисекунды,
created_at/updated_at/joined_at — секунды.
- signature везде Ed25519 (64 bytes) — NULL если не подписано.
- channel_id для DM: "dm:nodeA_nodeB" где A < B (канонический порядок).
- channel_id для групп: "group:" + SHA256(owner_node_id || name || timestamp).
- local_seq (из предыдущей версии) убран — дедупликация через UNIQUE индекс
(channel_id, author_node_id, timestamp) достаточна.
- deleted_at / soft delete не добавлены — можно добавить позже при необходимости.