using Microsoft.Data.Sqlite; namespace Calls.Api; /// Подключения к SQLite и схема. Файл базы лежит на постоянном томе (Storage:DataDir). public sealed class Db { public string DataDir { get; } private readonly string _cs; public Db(IConfiguration cfg) { DataDir = Path.GetFullPath(cfg["Storage:DataDir"] ?? "data"); Directory.CreateDirectory(DataDir); _cs = new SqliteConnectionStringBuilder { DataSource = Path.Combine(DataDir, "calls.db"), Cache = SqliteCacheMode.Shared, }.ToString(); } public SqliteConnection Open() { var c = new SqliteConnection(_cs); c.Open(); using var cmd = c.CreateCommand(); cmd.CommandText = "PRAGMA busy_timeout=5000;"; cmd.ExecuteNonQuery(); return c; } public void Init() { using var c = Open(); using var cmd = c.CreateCommand(); // Время — секунды Unix (UTC). cmd.CommandText = """ PRAGMA journal_mode=WAL; CREATE TABLE IF NOT EXISTS meta( key TEXT PRIMARY KEY, value TEXT NOT NULL ); INSERT OR IGNORE INTO meta(key, value) VALUES ('created_at', strftime('%Y-%m-%dT%H:%M:%fZ','now')); INSERT OR IGNORE INTO meta(key, value) VALUES ('schema', '1'); -- Одна постоянная комната Jitsi на workspace. Имя случайное, id workspace в нём нет. CREATE TABLE IF NOT EXISTS rooms( workspace_id TEXT PRIMARY KEY, room TEXT NOT NULL UNIQUE, created_at INTEGER NOT NULL, created_by TEXT NOT NULL ); -- Постоянная гостевая ссылка workspace. Токен ссылки = HMAC(Calls:LinkSecret, link_id); -- в базе только link_id и SHA-256 токена, сам токен без секрета не восстановить. CREATE TABLE IF NOT EXISTS guest_links( workspace_id TEXT PRIMARY KEY, link_id TEXT NOT NULL, token_hash TEXT NOT NULL UNIQUE, updated_at INTEGER NOT NULL, updated_by TEXT NOT NULL, updated_by_name TEXT NULL ); -- Одноразовые коды входа (окно звонка), живут Calls:CodeSeconds. Хранится хеш кода. CREATE TABLE IF NOT EXISTS join_codes( code_hash TEXT PRIMARY KEY, workspace_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT NOT NULL, email TEXT NULL, expires_at INTEGER NOT NULL ); -- Сессия окна звонка: по ней окно берёт новые пропуски Jitsi без токена CRM. Хранится хеш. CREATE TABLE IF NOT EXISTS call_sessions( session_hash TEXT PRIMARY KEY, kind TEXT NOT NULL CHECK (kind IN ('member','guest')), workspace_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT NOT NULL, email TEXT NULL, link_id TEXT NULL, created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS call_sessions_exp ON call_sessions(expires_at); """; cmd.ExecuteNonQuery(); } public string CreatedAt() { using var c = Open(); using var cmd = c.CreateCommand(); cmd.CommandText = "SELECT value FROM meta WHERE key='created_at'"; return (string)cmd.ExecuteScalar()!; } }