using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using SameBeat.Api.Hubs; using SameBeat.Application.Interfejsy; using SameBeat.Application.Komendy; namespace SameBeat.Api.Controllers; /// /// Sterowanie scena glowna przez hosta -- uklad i sloty (ETAP 3). Trasa zaczyna sie od "api/host", /// wiec jest automatycznie chroniona [Authorize] przez KonwencjaAutoryzacjiHosta. /// /// BEZPIECZENSTWO: kazda zmiana rozglaszana jest przez SignalR do WSZYSTKICH polaczonych klientow /// (rowniez anonimowych widzow) -- payload to zawsze StanScenyDto (numerSlotu + nick), NIGDY /// uczestnikId. Gdyby id wyciekl publicznie, dowolny widz moglby go uzyc do podszycia sie pod /// przypisanego uczestnika przy POST /api/join/token-sceny. /// [ApiController] [Route("api/host/stage")] public class HostStageController : ControllerBase { private readonly ISerwisSceny _serwisSceny; private readonly IHubContext _hubKontekst; public HostStageController(ISerwisSceny serwisSceny, IHubContext hubKontekst) { _serwisSceny = serwisSceny; _hubKontekst = hubKontekst; } /// Biezacy stan sceny -- do wyswietlenia w panelu hosta po odswiezeniu strony. [HttpGet] public async Task PobierzStanAsync(CancellationToken token) { var wynik = await _serwisSceny.PobierzStanScenyAsync(token); if (!wynik.Sukces || wynik.Dane is null) { return BadRequest(new { blad = wynik.Blad }); } return Ok(wynik.Dane); } [HttpPost("layout")] public async Task ZmienUkladAsync([FromBody] ZmienUkladScenyKomenda komenda, CancellationToken token) { var wynik = await _serwisSceny.ZmienUkladAsync(komenda.Uklad, token); if (!wynik.Sukces || wynik.Dane is null) { return BadRequest(new { blad = wynik.Blad }); } // Pelny stan po korekcie (sierocy sloty ponad nowy limit zostaly juz zwolnione) -- klient nie // musi dociagac osobnego GET /api/host/stage, zeby zobaczyc efekt. await _hubKontekst.Clients.All.SendAsync("UkladScenyZmieniony", wynik.Dane, cancellationToken: token); return Ok(wynik.Dane); } [HttpPost("slots/{numerSlotu:int}/assign")] public async Task PrzypiszSlotAsync(int numerSlotu, [FromBody] PrzypiszSlotKomenda komenda, CancellationToken token) { var wynik = await _serwisSceny.PrzypiszSlotAsync(numerSlotu, komenda.UczestnikId, token); if (!wynik.Sukces || wynik.Dane is null) { return BadRequest(new { blad = wynik.Blad }); } var przypisanySlot = wynik.Dane.Sloty.Find(s => s.NumerSlotu == numerSlotu); if (przypisanySlot is not null) { await _hubKontekst.Clients.All.SendAsync( "UczestnikDodanyDoSceny", new { numerSlotu = przypisanySlot.NumerSlotu, nick = przypisanySlot.Nick, uklad = wynik.Dane.Uklad }, cancellationToken: token); } await _hubKontekst.Clients.All.SendAsync("ScenaZmieniona", wynik.Dane, cancellationToken: token); return Ok(wynik.Dane); } /// /// Zwalnia slot -- uczestnik wraca do statusu Oczekuje. /// /// ETAP 7: oprocz "miekkiego" usuniecia w bazie ponizej (SerwisSceny.ZwolnijSlotAsync), serwis TWARDO /// wymusza to tez w LiveKit (ISerwisAdministracjiLiveKit.OdbierzUprawnieniaScenyAsync, best effort) -- /// uczestnik fizycznie traci canPublishSources (kamera+mikrofon), niezaleznie od tego, czy klient /// w ogole obsluzy zdarzenie SignalR ponizej. /// [HttpDelete("slots/{numerSlotu:int}")] public async Task ZwolnijSlotAsync(int numerSlotu, CancellationToken token) { var wynik = await _serwisSceny.ZwolnijSlotAsync(numerSlotu, token); if (!wynik.Sukces || wynik.Dane is null) { return BadRequest(new { blad = wynik.Blad }); } await _hubKontekst.Clients.All.SendAsync( "UczestnikUsunietyZeSceny", new { numerSlotu, uklad = wynik.Dane.Uklad }, cancellationToken: token); await _hubKontekst.Clients.All.SendAsync("ScenaZmieniona", wynik.Dane, cancellationToken: token); return Ok(wynik.Dane); } }