Files
grendervill 50709396cd feat(dm): хранилище и миграция звонков в беседах (Фаза 7)
Звонок в личной и групповой беседе живёт в своей таблице: в voice_states
guild_id обязателен, а у беседы сервера нет. Миграция 00026 добавляет
dm_calls (статус, media, длительность, причина завершения) и
dm_call_participants (состояние участника и флаги микрофона, камеры и
экрана); уникальный частичный индекс держит один идущий звонок на беседу.

В DMParticipantProfiles сравнение с владельцем обёрнуто в COALESCE: у 1:1
dm_owner_id пуст, и NULL нельзя было прочитать в int — ручка звонка в
личной беседе падала на 500.

Тесты: жизненный цикл звонка в хранилище (гудки, active на втором
участнике, история, повторный звонок) и выборка сторожем.
2026-09-26 17:02:51 +03:00

52 lines
2.8 KiB
SQL

-- +goose Up
-- Фаза 7: звонки в личных и групповых беседах (AGENT.md 7.8, 7.14).
--
-- Звонок — отдельная сущность беседы: у него своя комната LiveKit, своё
-- состояние участников и своя история (кто звонил, когда, сколько длился,
-- кто пропустил). В voice_states звонок не помещается: там guild_id
-- обязателен, а у личной беседы сервера нет.
--
-- Статусы звонка (dm_calls.status): ringing — идут гудки, active — хотя бы
-- двое в комнате, ended — завершён. Состояния участника
-- (dm_call_participants.state): invited — приглашён и ещё не ответил,
-- joined — в звонке, declined — отклонил, left — вышел, missed — не ответил.
CREATE TABLE dm_calls (
id INTEGER PRIMARY KEY,
channel_id INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
initiator_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
status TEXT NOT NULL,
-- media: audio | video — чем открыли звонок, клиент показывает это в истории.
media TEXT NOT NULL DEFAULT 'audio',
created_at TEXT NOT NULL,
-- started_at — момент, когда звонок стал active: от него считается
-- длительность разговора, а не от гудков.
started_at TEXT,
ended_at TEXT,
ended_by INTEGER REFERENCES users (id) ON DELETE SET NULL,
-- end_reason: completed | canceled | declined | missed | failed.
end_reason TEXT
);
CREATE INDEX dm_calls_channel_idx ON dm_calls (channel_id, id DESC);
-- В беседе одновременно идёт не больше одного звонка: гонку двух «позвонить»
-- закрывает сама база, а не проверка в приложении.
CREATE UNIQUE INDEX dm_calls_live_channel_idx ON dm_calls (channel_id) WHERE status <> 'ended';
CREATE TABLE dm_call_participants (
call_id INTEGER NOT NULL REFERENCES dm_calls (id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
state TEXT NOT NULL,
invited_at TEXT NOT NULL,
joined_at TEXT,
left_at TEXT,
self_mute INTEGER NOT NULL DEFAULT 0,
self_deaf INTEGER NOT NULL DEFAULT 0,
camera INTEGER NOT NULL DEFAULT 0,
screen INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (call_id, user_id)
);
CREATE INDEX dm_call_participants_user_idx ON dm_call_participants (user_id, call_id DESC);
-- +goose Down
DROP TABLE dm_call_participants;
DROP TABLE dm_calls;