101 lines
3.9 KiB
C#
101 lines
3.9 KiB
C#
using Microsoft.Data.Sqlite;
|
||
|
||
namespace Calls.Api;
|
||
|
||
/// <summary>Подключения к SQLite и схема. Файл базы лежит на постоянном томе (Storage:DataDir).</summary>
|
||
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()!;
|
||
}
|
||
}
|