using Microsoft.Extensions.Logging; using SameBeat.Application.DTO; using SameBeat.Application.Interfejsy; using SameBeat.Application.Komendy; using SameBeat.Domain.Encje; using SameBeat.Domain.Enumy; using SameBeat.Domain.Wyjatki; namespace SameBeat.Application.Serwisy; /// /// Logika poczekalni -- dolaczanie nowych uczestnikow do biezacego wydarzenia (z tozsamoscia LiveKit /// generowana serwerowo) oraz zarzadzanie nimi przez hosta. Akceptacja/wpuszczenie na scene to ETAP 3 -- /// tutaj host moze tylko odrzucic albo zbanowac. /// public sealed class SerwisPoczekalni : ISerwisPoczekalni { private readonly IRepozytoriumWydarzen _repozytoriumWydarzen; private readonly IRepozytoriumPoczekalni _repozytoriumPoczekalni; private readonly ISerwisTokenowLiveKit _serwisTokenowLiveKit; private readonly IUslugaHaszowaniaSekretowSesji _uslugaHaszowaniaSekretowSesji; private readonly IJednostkaPracy _jednostkaPracy; private readonly string _urlLiveKit; private readonly string _nazwaPokojuPoczekalnia; private readonly ILogger _logger; public SerwisPoczekalni( IRepozytoriumWydarzen repozytoriumWydarzen, IRepozytoriumPoczekalni repozytoriumPoczekalni, ISerwisTokenowLiveKit serwisTokenowLiveKit, IUslugaHaszowaniaSekretowSesji uslugaHaszowaniaSekretowSesji, IJednostkaPracy jednostkaPracy, string urlLiveKit, string nazwaPokojuPoczekalnia, ILogger logger) { _repozytoriumWydarzen = repozytoriumWydarzen; _repozytoriumPoczekalni = repozytoriumPoczekalni; _serwisTokenowLiveKit = serwisTokenowLiveKit; _uslugaHaszowaniaSekretowSesji = uslugaHaszowaniaSekretowSesji; _jednostkaPracy = jednostkaPracy; _urlLiveKit = urlLiveKit; _nazwaPokojuPoczekalnia = nazwaPokojuPoczekalnia; _logger = logger; } public async Task> DolaczAsync(DolaczKomenda komenda, CancellationToken token = default) { // "Biezace" wydarzenie = najswiezsze utworzone, ta sama definicja co uzywa juz ISerwisWydarzen // dla publicznego statusu na stronie glownej. var wydarzenie = await _repozytoriumWydarzen.PobierzOstatnieAsync(token); if (wydarzenie is null) { _logger.LogInformation("Proba dolaczenia do poczekalni bez zadnego wydarzenia w bazie."); return WynikOperacji.Niepowodzenie("Brak aktywnego wydarzenia, sprobuj pozniej."); } UczestnikOczekujacy uczestnik; try { uczestnik = UczestnikOczekujacy.Dolacz(wydarzenie.Id, komenda.Nick); } catch (WyjatekDomenowy wyjatek) { return WynikOperacji.Niepowodzenie(wyjatek.Message); } if (string.IsNullOrWhiteSpace(_urlLiveKit)) { // Ten sam powod co w SerwisDostepuLiveKit -- brak konfiguracji LiveKit ma dac czytelny // blad biznesowy, nie NullReferenceException/500 na swiezym srodowisku. _logger.LogWarning("Dolaczenie odrzucone -- adres serwera LiveKit nie jest skonfigurowany."); return WynikOperacji.Niepowodzenie("Usluga LiveKit nie jest jeszcze skonfigurowana na serwerze."); } // Sekret sesji (ETAP 3): jedyny dowod tozsamosci "to ja", ktory uczestnik bez konta bedzie mogl // pozniej pokazac (status w poczekalni, wymiana na token sceny). Jawna wartosc leci TYLKO w tej // odpowiedzi -- w bazie lÄ…duje wylacznie jej hash, dokladnie jak przy hasle hosta. var sekretJawny = _uslugaHaszowaniaSekretowSesji.WygenerujSekret(); uczestnik.UstawSekretSesji(_uslugaHaszowaniaSekretowSesji.Haszuj(uczestnik, sekretJawny)); _repozytoriumPoczekalni.Dodaj(uczestnik); await _jednostkaPracy.ZapiszZmianyAsync(token); // Token Uczestnika: kamera tak, mikrofon NIGDY na starcie -- to wymusza sam // SerwisTokenowLiveKit przez canPublishSources:["camera"] w tokenie. var liveKitToken = _serwisTokenowLiveKit.WygenerujToken(uczestnik.LiveKitIdentity, uczestnik.Nick, RolaUzytkownika.Uczestnik, _nazwaPokojuPoczekalnia); _logger.LogInformation("Uczestnik {UczestnikId} dolaczyl do poczekalni wydarzenia {WydarzenieId}.", uczestnik.Id, wydarzenie.Id); return WynikOperacji.Ok(new OdpowiedzDolaczeniaDto(uczestnik.Id, sekretJawny, _urlLiveKit, liveKitToken)); } public async Task>> PobierzOczekujacychAsync(CancellationToken token = default) { var wydarzenie = await _repozytoriumWydarzen.PobierzOstatnieAsync(token); if (wydarzenie is null) { // Brak wydarzenia to normalny stan (np. host jeszcze niczego nie utworzyl) -- pusta lista, nie blad. return WynikOperacji>.Ok(new List()); } var oczekujacy = await _repozytoriumPoczekalni.PobierzOczekujacychDlaWydarzeniaAsync(wydarzenie.Id, token); var dto = oczekujacy .Select(u => new UczestnikOczekujacyDto(u.Id, u.Nick, u.LiveKitIdentity, u.DolaczylUtc, u.Status)) .ToList(); return WynikOperacji>.Ok(dto); } public async Task OdrzucAsync(Guid uczestnikId, CancellationToken token = default) { var uczestnik = await _repozytoriumPoczekalni.ZnajdzPoIdAsync(uczestnikId, token); if (uczestnik is null) { return WynikOperacji.Niepowodzenie("Nie znaleziono uczestnika o podanym identyfikatorze."); } uczestnik.Odrzuc(); await _jednostkaPracy.ZapiszZmianyAsync(token); return WynikOperacji.Ok(); } public async Task ZbanujAsync(Guid uczestnikId, CancellationToken token = default) { var uczestnik = await _repozytoriumPoczekalni.ZnajdzPoIdAsync(uczestnikId, token); if (uczestnik is null) { return WynikOperacji.Niepowodzenie("Nie znaleziono uczestnika o podanym identyfikatorze."); } uczestnik.Zbanuj(); await _jednostkaPracy.ZapiszZmianyAsync(token); return WynikOperacji.Ok(); } public WynikOperacji WygenerujTokenPodgladuHosta(string nickHosta) { if (string.IsNullOrWhiteSpace(_urlLiveKit)) { _logger.LogWarning("Token podgladu hosta odrzucony -- adres serwera LiveKit nie jest skonfigurowany."); return WynikOperacji.Niepowodzenie("Usluga LiveKit nie jest jeszcze skonfigurowana na serwerze."); } // Host dostaje token do pokoju POCZEKALNI, nie glownej sceny -- na tym etapie tylko tu ma // cokolwiek do podejrzenia (kamery oczekujacych, zanim ich zaakceptuje albo odrzuci). var liveKitToken = _serwisTokenowLiveKit.WygenerujToken(nickHosta, nickHosta, RolaUzytkownika.Host, _nazwaPokojuPoczekalnia); return WynikOperacji.Ok(new OdpowiedzTokenuLiveKitDto(liveKitToken, _urlLiveKit)); } }