mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-03-28 17:42:45 +01:00
feat(v2): Add SQLite schema with 10 tables, indexes and FTS5
Tables: device, contacts, channels, channel_messages, direct_messages, acks, echoes, paths, advertisements, read_status. Includes schema_version for migrations, FTS5 virtual tables with auto-sync triggers for full-text search on messages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -76,6 +76,7 @@ data/
|
|||||||
# ============================================
|
# ============================================
|
||||||
*.log
|
*.log
|
||||||
*.sql
|
*.sql
|
||||||
|
!app/schema.sql
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.db
|
*.db
|
||||||
|
|
||||||
|
|||||||
198
app/schema.sql
Normal file
198
app/schema.sql
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
-- mc-webui v2 SQLite Schema
|
||||||
|
-- WAL mode and foreign keys are enabled programmatically in Database.__init__
|
||||||
|
|
||||||
|
-- Schema versioning for future migrations
|
||||||
|
CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
|
version INTEGER PRIMARY KEY,
|
||||||
|
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
INSERT OR IGNORE INTO schema_version (version) VALUES (1);
|
||||||
|
|
||||||
|
-- Device identity and settings
|
||||||
|
CREATE TABLE IF NOT EXISTS device (
|
||||||
|
id INTEGER PRIMARY KEY CHECK (id = 1), -- singleton row
|
||||||
|
public_key TEXT NOT NULL DEFAULT '',
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
self_info TEXT -- JSON blob with full device info
|
||||||
|
);
|
||||||
|
|
||||||
|
-- All known contacts (replaces contacts_cache.jsonl)
|
||||||
|
CREATE TABLE IF NOT EXISTS contacts (
|
||||||
|
public_key TEXT PRIMARY KEY, -- hex, lowercase
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
type INTEGER DEFAULT 0, -- node type from device
|
||||||
|
flags INTEGER DEFAULT 0,
|
||||||
|
out_path TEXT DEFAULT '', -- outgoing path string
|
||||||
|
out_path_len INTEGER DEFAULT 0,
|
||||||
|
last_advert TEXT, -- ISO 8601 timestamp
|
||||||
|
adv_lat REAL, -- GPS latitude from advert
|
||||||
|
adv_lon REAL, -- GPS longitude from advert
|
||||||
|
first_seen TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
last_seen TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
source TEXT DEFAULT 'advert', -- 'advert', 'device', 'manual'
|
||||||
|
is_protected INTEGER DEFAULT 0, -- 1 = protected from cleanup
|
||||||
|
lastmod TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Channel configuration
|
||||||
|
CREATE TABLE IF NOT EXISTS channels (
|
||||||
|
idx INTEGER PRIMARY KEY, -- channel index (0-7)
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
secret TEXT, -- channel secret/key (hex)
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Channel messages (replaces CHAN/SENT_CHAN from .msgs)
|
||||||
|
CREATE TABLE IF NOT EXISTS channel_messages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
channel_idx INTEGER NOT NULL DEFAULT 0,
|
||||||
|
sender TEXT NOT NULL DEFAULT '',
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
timestamp INTEGER NOT NULL DEFAULT 0, -- unix epoch
|
||||||
|
sender_timestamp INTEGER, -- sender's clock
|
||||||
|
is_own INTEGER NOT NULL DEFAULT 0, -- 1 = sent by us
|
||||||
|
txt_type INTEGER DEFAULT 0,
|
||||||
|
snr REAL,
|
||||||
|
path_len INTEGER,
|
||||||
|
pkt_payload TEXT, -- for echo matching
|
||||||
|
raw_json TEXT, -- original JSON line
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Direct messages (replaces PRIV/SENT_MSG from .msgs)
|
||||||
|
CREATE TABLE IF NOT EXISTS direct_messages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
contact_pubkey TEXT, -- FK to contacts (nullable for unknown)
|
||||||
|
direction TEXT NOT NULL CHECK (direction IN ('in', 'out')),
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
timestamp INTEGER NOT NULL DEFAULT 0, -- unix epoch
|
||||||
|
sender_timestamp INTEGER,
|
||||||
|
txt_type INTEGER DEFAULT 0,
|
||||||
|
snr REAL,
|
||||||
|
path_len INTEGER,
|
||||||
|
expected_ack TEXT, -- ACK code for delivery tracking
|
||||||
|
signature TEXT, -- dedup signature
|
||||||
|
raw_json TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
FOREIGN KEY (contact_pubkey) REFERENCES contacts(public_key) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ACK tracking (replaces .acks.jsonl)
|
||||||
|
CREATE TABLE IF NOT EXISTS acks (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
expected_ack TEXT NOT NULL, -- ACK code to match
|
||||||
|
received_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
snr REAL,
|
||||||
|
rssi REAL,
|
||||||
|
route_type TEXT, -- 'direct', 'flood', etc.
|
||||||
|
is_retry INTEGER DEFAULT 0,
|
||||||
|
dm_id INTEGER, -- FK to direct_messages (nullable)
|
||||||
|
FOREIGN KEY (dm_id) REFERENCES direct_messages(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Echo tracking (replaces .echoes.jsonl)
|
||||||
|
CREATE TABLE IF NOT EXISTS echoes (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
pkt_payload TEXT NOT NULL, -- matches channel_messages.pkt_payload
|
||||||
|
path TEXT, -- relay path string
|
||||||
|
snr REAL,
|
||||||
|
received_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
direction TEXT DEFAULT 'incoming', -- 'sent' or 'incoming'
|
||||||
|
cm_id INTEGER, -- FK to channel_messages (nullable)
|
||||||
|
FOREIGN KEY (cm_id) REFERENCES channel_messages(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Path tracking (replaces .path.jsonl)
|
||||||
|
CREATE TABLE IF NOT EXISTS paths (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
contact_pubkey TEXT,
|
||||||
|
pkt_payload TEXT,
|
||||||
|
path TEXT,
|
||||||
|
snr REAL,
|
||||||
|
path_len INTEGER,
|
||||||
|
received_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Advertisements (replaces .adverts.jsonl)
|
||||||
|
CREATE TABLE IF NOT EXISTS advertisements (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
public_key TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
type INTEGER DEFAULT 0,
|
||||||
|
lat REAL,
|
||||||
|
lon REAL,
|
||||||
|
timestamp INTEGER NOT NULL DEFAULT 0,
|
||||||
|
snr REAL,
|
||||||
|
raw_payload TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Read status tracking (replaces .read_status.json)
|
||||||
|
CREATE TABLE IF NOT EXISTS read_status (
|
||||||
|
key TEXT PRIMARY KEY, -- 'chan_0', 'dm_<pubkey>', etc.
|
||||||
|
last_seen_ts INTEGER DEFAULT 0, -- unix timestamp
|
||||||
|
is_muted INTEGER DEFAULT 0, -- 1 = muted (channels only)
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Indexes
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cm_channel_ts ON channel_messages(channel_idx, timestamp);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cm_pkt ON channel_messages(pkt_payload);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_dm_contact ON direct_messages(contact_pubkey, timestamp);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_dm_ack ON direct_messages(expected_ack);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_acks_code ON acks(expected_ack);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_echoes_pkt ON echoes(pkt_payload);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_adv_pubkey ON advertisements(public_key, timestamp);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_contacts_name ON contacts(name);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Full-Text Search (FTS5)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE VIRTUAL TABLE IF NOT EXISTS channel_messages_fts USING fts5(
|
||||||
|
content,
|
||||||
|
content=channel_messages,
|
||||||
|
content_rowid=id
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIRTUAL TABLE IF NOT EXISTS direct_messages_fts USING fts5(
|
||||||
|
content,
|
||||||
|
content=direct_messages,
|
||||||
|
content_rowid=id
|
||||||
|
);
|
||||||
|
|
||||||
|
-- FTS triggers: keep FTS index in sync with source tables
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS cm_fts_insert AFTER INSERT ON channel_messages BEGIN
|
||||||
|
INSERT INTO channel_messages_fts(rowid, content) VALUES (new.id, new.content);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS cm_fts_delete AFTER DELETE ON channel_messages BEGIN
|
||||||
|
INSERT INTO channel_messages_fts(channel_messages_fts, rowid, content)
|
||||||
|
VALUES ('delete', old.id, old.content);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS cm_fts_update AFTER UPDATE OF content ON channel_messages BEGIN
|
||||||
|
INSERT INTO channel_messages_fts(channel_messages_fts, rowid, content)
|
||||||
|
VALUES ('delete', old.id, old.content);
|
||||||
|
INSERT INTO channel_messages_fts(rowid, content) VALUES (new.id, new.content);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS dm_fts_insert AFTER INSERT ON direct_messages BEGIN
|
||||||
|
INSERT INTO direct_messages_fts(rowid, content) VALUES (new.id, new.content);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS dm_fts_delete AFTER DELETE ON direct_messages BEGIN
|
||||||
|
INSERT INTO direct_messages_fts(direct_messages_fts, rowid, content)
|
||||||
|
VALUES ('delete', old.id, old.content);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS dm_fts_update AFTER UPDATE OF content ON direct_messages BEGIN
|
||||||
|
INSERT INTO direct_messages_fts(direct_messages_fts, rowid, content)
|
||||||
|
VALUES ('delete', old.id, old.content);
|
||||||
|
INSERT INTO direct_messages_fts(rowid, content) VALUES (new.id, new.content);
|
||||||
|
END;
|
||||||
Reference in New Issue
Block a user