feat(voice): вход в голосовые комнаты, voice states и модерация (Фаза 3)

AGENT.md 7.14, начало Фазы 3:

- `internal/voice`: собственный подписыватель токенов LiveKit (HS256, grants
  roomJoin/canPublish/canSubscribe/canPublishData) и адрес комнаты
  `guild_{g}_channel_{c}` — без внешних зависимостей;
- таблица `voice_states` (миграция 00009) и store-методы: вход, флаги
  (микрофон, звук, камера, экран), серверный мьют и глушение, перемещение,
  выход, подсчёт участников комнаты;
- ручки: POST /channels/{id}/voice/join (проверяет CONNECT_VOICE, тип комнаты,
  лимит участников и выдаёт токен), POST /channels/{id}/voice/leave,
  PATCH /guilds/{id}/voice-states/@me, GET /guilds/{id}/voice-states,
  PATCH /guilds/{id}/voice-states/{user_id} (MUTE_MEMBERS/DEAFEN_MEMBERS,
  аудит), POST /guilds/{id}/voice-states/{user_id}/move (MOVE_MEMBERS);
- события VOICE_STATE_UPDATE всем участникам сервера, системные записи о входе
  в голосовую комнату, READY отдаёт голосовые состояния серверов;
- конфиг: LIVEKIT_API_KEY/SECRET/URL приложению, публичный адрес
  `LIVEKIT_PUBLIC_URL` (по умолчанию ws(s)://<домен>/rtc) в установщике и
  compose; `/api/v1/meta` сообщает voice_enabled и voice_url;
- тесты: claims токена LiveKit и подпись, выключенный голос, имя комнаты, вход
  и флаги, запрет мьюта без прав, перемещение и выход.
This commit is contained in:
2026-09-20 18:04:59 +03:00
parent 435c4deb9c
commit ecd52c11d3
13 changed files with 1045 additions and 19 deletions
+5
View File
@@ -22,6 +22,7 @@ import (
"glchat/internal/permissions"
"glchat/internal/source"
"glchat/internal/store"
"glchat/internal/voice"
)
// Deps — зависимости HTTP-слоя: хранилище и сервис аутентификации.
@@ -59,6 +60,8 @@ type Server struct {
// отправки; словарь ограничен по размеру (AGENT.md 7.5).
slowmodeMu sync.Mutex
slowmode map[string]time.Time
// voice — подписыватель токенов LiveKit (AGENT.md 7.14).
voice *voice.TokenIssuer
// presence — время последнего обновления last_seen по пользователю.
presenceMu sync.Mutex
presence map[uint64]time.Time
@@ -89,6 +92,7 @@ func New(cfg config.Config, db *database.DB, logger *slog.Logger, deps Deps) *Se
inviteLimiter: httpx.NewRateLimiterWindow(10, 24*time.Hour, 10),
slowmode: map[string]time.Time{},
presence: map[uint64]time.Time{},
voice: voice.NewIssuer(cfg.LiveKitAPIKey, cfg.LiveKitAPISecret, time.Hour),
}
switch {
case deps.Permissions != nil:
@@ -114,6 +118,7 @@ func New(cfg config.Config, db *database.DB, logger *slog.Logger, deps Deps) *Se
s.registerFileRoutes(s.api, apiRouter)
s.registerSocialRoutes(s.api)
s.registerEmojiRoutes(s.api, apiRouter)
s.registerVoiceRoutes(s.api)
}
apiRouter.Get("/openapi.json", s.handleOpenAPI)
})