Files
crm-calls/calls/backend/Calls.Api/Db.cs

135 lines
5.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
-- Выданные пропуски: какие комнаты отслеживать и кто гость (имя на странице звонка).
CREATE TABLE IF NOT EXISTS pass_log(
room TEXT NOT NULL,
workspace_id TEXT NOT NULL,
name TEXT NOT NULL,
kind TEXT NOT NULL,
issued_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS pass_log_room ON pass_log(room, issued_at);
CREATE INDEX IF NOT EXISTS pass_log_time ON pass_log(issued_at);
-- Звонки и их запись. session_id = каталог записи Jibri (/recordings/<session_id>).
-- status: active (идёт, без записи) → recording → ended → processing/waiting/error → done | skipped.
CREATE TABLE IF NOT EXISTS calls(
id TEXT PRIMARY KEY,
room TEXT NOT NULL,
workspace_id TEXT NOT NULL,
status TEXT NOT NULL,
started_at INTEGER NOT NULL,
last_seen_at INTEGER NOT NULL,
ended_at INTEGER NULL,
record_started_at INTEGER NULL,
session_id TEXT NULL,
participants TEXT NOT NULL DEFAULT '[]',
attempts INTEGER NOT NULL DEFAULT 0,
next_attempt_at INTEGER NOT NULL DEFAULT 0,
error TEXT NULL,
storage_path TEXT NULL,
audio_url TEXT NULL,
page_id TEXT NULL,
updated_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS calls_status ON calls(status);
""";
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()!;
}
}