feat(regions): add data layer for per-channel region scopes

Introduces the SQLite-backed region registry and channel->region mapping
that will drive the per-channel flood-scope feature. No UI or device
wiring yet; those land in subsequent PRs.

- schema.sql: new `regions` and `channel_scopes` tables + partial index
  on the default flag.
- database.py: CRUD helpers for regions (create/list/get/delete/default)
  and channel_scopes (set/get/bulk-load) with ON DELETE CASCADE.
- app/meshcore/regions.py: pure helpers for SHA256('#'+name)[:16] key
  derivation and firmware-compatible name validation (mirrors the
  `RegionMap::is_name_char` rule `c in {-,$,#} or c>='0' or c>='A'`).
- tests/test_regions.py: known SHA256 vectors, validator coverage
  (incl. the firmware quirk that `_` and other 0x5B-0x60 chars are
  admitted), and CRUD + cascade integration tests.
This commit is contained in:
MarekWo
2026-04-24 07:12:55 +02:00
parent daf9c5c0db
commit 8e353407d3
4 changed files with 416 additions and 0 deletions
+18
View File
@@ -43,6 +43,23 @@ CREATE TABLE IF NOT EXISTS channels (
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Region registry (user-curated MeshCore flood scopes)
CREATE TABLE IF NOT EXISTS regions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE, -- firmware-safe name, e.g. 'pl-ma'
key_hex TEXT NOT NULL, -- 32 hex chars = 16-byte scope key
is_default INTEGER NOT NULL DEFAULT 0, -- mirrors firmware CMD_GET_DEFAULT_FLOOD_SCOPE
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Per-channel region mapping (absent row = no override; firmware default applies)
CREATE TABLE IF NOT EXISTS channel_scopes (
channel_idx INTEGER PRIMARY KEY,
region_id INTEGER NOT NULL REFERENCES regions(id) ON DELETE CASCADE,
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,
@@ -188,6 +205,7 @@ 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);
CREATE INDEX IF NOT EXISTS idx_cp_contact ON contact_paths(contact_pubkey, sort_order);
CREATE INDEX IF NOT EXISTS idx_regions_default ON regions(is_default) WHERE is_default = 1;
-- ============================================================
-- Full-Text Search (FTS5)