# Gotowość MVP -- checklist

Ten dokument to jedna lista kontrolna z pkt. 50 pierwotnej specyfikacji
projektu ("Kryteria gotowego MVP") -- **każdy punkt sprawdzony bezpośrednio
w kodzie i w `ARCHITEKTURA.md`/`BEZPIECZENSTWO.md`**, nie na podstawie planów
ani założeń. `[x]` = naprawdę działa, sprawdzone w kodzie. `[ ]` = brakuje
albo działa tylko częściowo -- przy każdym takim punkcie jest napisane
wprost, czego brakuje i co trzeba zrobić, żeby domknąć.

> **Stan na 2026-08-21.** Backend i frontend w trakcie tej checklisty były w
> części dalej aktywnie rozwijane (widoczne w kodzie jako "ETAP 8") -- ta
> lista opisuje stan zastany w chwili sprawdzania, nie plan na przyszłość.

## Lista

- [x] **Host może się bezpiecznie zalogować.**
  `POST /api/auth/login` (`AuthController.cs`) sprawdza hasło przez PBKDF2
  (`UslugaHaszowaniaHasel`, `Microsoft.AspNetCore.Identity.PasswordHasher`),
  wydaje token JWT w ciasteczku httpOnly (nie w
  JS), a `/api/host/...` jest automatycznie chronione `[Authorize]`
  (`KonwencjaAutoryzacjiHosta`). Do tego: limit 5 prób logowania / 5 minut
  na IP oraz blokada konta na 15 minut po 5 nieudanych próbach (ETAP 7,
  patrz `BEZPIECZENSTWO.md`).

- [x] **Widz może wejść na `/party`.**
  Trasa `/party` jest zdefiniowana wprost w `Router.tsx` i renderuje
  komponent `Impreza`, który pobiera bieżące wydarzenie z
  `GET /api/events/current` (ETAP 1/3).

- [x] **Słyszy muzykę.**
  ETAP 4: backend trzyma tylko "zegar" (od kiedy i od którego miejsca gra
  utwór), rozsyłany przez SignalR; każda przeglądarka -- widza i hosta --
  sama odtwarza ten sam plik audio pod tym samym adresem URL
  (`AudioSerwis.ts`), z bieżącą korektą pozycji w czasie.

- [x] **Uczestnik może włączyć kamerę.**
  ETAP 2: `/join` prosi przeglądarkę o zgodę na kamerę, a backend
  (`POST /api/join`) wydaje token LiveKit z prawem pokazania **wyłącznie
  kamery** (LiveKit fizycznie odrzuci próbę wysłania dźwięku na tym etapie).

- [x] **Uczestnik trafia do poczekalni.**
  ETAP 2: `POST /api/join` tworzy wpis w poczekalni
  (`IRepozytoriumPoczekalni` / encja `UczestnikOczekujacy`) w osobnym
  pokoju LiveKit "poczekalnia", niewidocznym dla widzów.

- [x] **Host widzi jego podgląd.**
  ETAP 2: `Host.tsx` (`pokazPodglad`) łączy się do pokoju poczekalni i
  subskrybuje strumień wideo konkretnego uczestnika po jego
  `LiveKitIdentity`, z trzema akcjami: podejrzyj / odrzuć / zbanuj.

- [x] **Host przeciąga go na slot.**
  ETAP 3: w `Host.tsx` to jest naprawdę przeciąganie myszką (natywne
  `draggable` / `onDragStart` / `onDrop`, nie tylko przycisk), które woła
  `POST /api/host/stage/slots/{numer}/assign`.

- [x] **Wszyscy widzowie widzą zmianę.**
  ETAP 3: backend rozsyła przez SignalR zdarzenia sceny (np.
  `UczestnikDodanyDoSceny`, `ScenaZmieniona`) do wszystkich klientów naraz,
  z payloadem ograniczonym do numeru slotu i nicku.

- [x] **Host może zmieniać układ.**
  ETAP 3: enum `UkladSceny` + `ZmienUkladScenyKomenda`, zdarzenie SignalR
  `UkladScenyZmieniony` do wszystkich klientów.

- [ ] **Uczestnik może poprosić o mikrofon.**
  **Brakuje.** W całym kodzie (backend i frontend) nie ma żadnego endpointu
  ani przycisku typu "zgłoś się o mikrofon" -- dziś wyłącznie host
  decyduje i sam przyznaje mikrofon komuś, kto już jest na scenie
  (`HostMicrophoneController`), bez żadnego sygnału inicjowanego przez
  uczestnika. Do zrobienia: nowy endpoint (np. z prywatnym sekretem sesji
  uczestnika, analogicznie do sprawdzania miejsca na scenie w ETAPIE 3) +
  zdarzenie SignalR do grupy "host" + przycisk w UI uczestnika + miejsce w
  panelu hosta, żeby takie zgłoszenia zobaczyć.

- [x] **Host daje dokładnie 5 sekund.**
  ETAP 5: `POST /api/host/mikrofon/{id}/przyznaj` przyjmuje `CzasSekundy`
  (panel oferuje 5/10/15 s), a `SerwisTimeraMikrofonu` odlicza dokładnie
  tyle, ile host wybrał, własnym zegarem serwera -- 5 sekund jest jedną z
  dostępnych, dokładnie egzekwowanych opcji, nie przybliżeniem.

- [x] **Muzyka się ścisza.**
  ETAP 5: `AudioSerwis.przyciszDlaMikrofonu` (ducking do 45% głośności
  mastera, płynnie w 300 ms) na KAŻDYM kliencie, wyzwalane zdarzeniem
  SignalR `MikrofonPrzyznany`.

- [x] **Użytkownik mówi.**
  To bezpośrednia konsekwencja wcześniejszych punktów -- po przyznaniu
  mikrofonu token LiveKit uczestnika ma już prawo do publikacji dźwięku
  (`SerwisTokenowLiveKit`), więc transmisja działa bez dodatkowej logiki.

- [x] **Po 5 sekundach mikrofon jest odebrany przez backend.**
  ETAP 5: `SerwisTimeraMikrofonu` (`BackgroundService` działający w tle)
  liczy czas WYŁĄCZNIE po stronie serwera i sam woła LiveKit Room Service
  (`SerwisAdministracjiLiveKit`), żeby fizycznie odebrać prawo do
  publikacji dźwięku -- niezależnie od tego, co robi przeglądarka
  uczestnika.

- [x] **Muzyka wraca.**
  ETAP 5: `AudioSerwis.przywrocPoMikrofonie`, wyzwalane zdarzeniem SignalR
  `MikrofonOdebrany`.

- [x] **Użytkownik nie może samowolnie ponownie uruchomić mikrofonu.**
  ETAP 5 + ETAP 7: dwie niezależne warstwy -- zapis w bazie (kto aktualnie
  ma głos) i fizyczne odebranie prawa publikacji w LiveKit Room Service.
  Nawet zmodyfikowana przeglądarka nie obejdzie drugiej warstwy, bo to
  serwer streamingu, nie klient, decyduje, czy dźwięk przejdzie dalej
  (patrz `BEZPIECZENSTWO.md`).

- [x] **Host może usunąć użytkownika.**
  ETAP 7: `DELETE /api/host/stage/slots/{numer}` zmienia status w bazie
  ORAZ woła `SerwisAdministracjiLiveKit.OdbierzUprawnieniaScenyAsync`, żeby
  fizycznie odebrać prawo publikacji -- to jest twarde usunięcie, nie tylko
  "grzecznościowa" prośba do przeglądarki uczestnika.

- [x] **Reconnect działa.**
  Kanał sterowania (SignalR, `.withAutomaticReconnect()`) i obraz/dźwięk
  (wbudowany mechanizm reconnect w bibliotece `livekit-client`) same
  próbują wrócić po zerwaniu internetu, bez klikania -- a muzyka po
  powrocie doskakuje na właściwe miejsce (`AudioSerwis`, próg 2 sekund).
  Panel hosta **dołącza automatycznie z powrotem** do grupy "host" po
  reconnect SignalR -- `SignalRSerwis.ts` podpina `DolaczDoGrupyHosta()` pod
  zdarzenie `onreconnected()` połączenia (flaga
  `dolaczycDoGrupyHostaPoReconnect`, ustawiana raz przy pierwszym
  dołączeniu), więc nie trzeba ręcznie odświeżać strony po chwilowej
  utracie internetu. To był wykryty w trakcie tego etapu brak, domknięty
  jeszcze w tej samej sesji prac -- szczegóły mechanizmu patrz sekcja
  ETAP 8 w `ARCHITEKTURA.md`.

- [x] **Podstawowe logi działają.**
  Serilog skonfigurowany od pierwszej linijki startu API (`Program.cs`,
  `KonfiguracjaSerilog.cs`): konsola + plik z rotacją dzienną w
  `SameBeat.Api/logs/` (30 dni wstecz), z zasadą "nigdy nie loguj haseł ani
  tokenów". ETAP 8 dołożył też `GET /api/health` i
  `GET /api/host/diagnostyka` jako uzupełnienie (liczba połączeń SignalR,
  stan bazy danych, uptime procesu).

- [ ] **System działa przez HTTPS.**
  **Nie (jeszcze) -- to zależy od wdrożenia na VPS, nie od kodu.** W
  backendzie `app.UseHttpsRedirection()` już jest włączone, a `VPS.md` ma
  gotowy, szczegółowy plan (Nginx + certyfikat Let's Encrypt, automatyczne
  odnawianie). Ale sam serwer VPS **nie został jeszcze postawiony** --
  `VPS.md` wprost mówi na początku: "to jest PLAN -- nic z tego nie zostało
  jeszcze wykonane". Lokalnie (`localhost`) przeglądarki i tak pozwalają na
  kamerę/mikrofon bez HTTPS, więc to nie blokuje pracy deweloperskiej, ale
  blokuje realne wydarzenie dostępne w internecie -- bez HTTPS przeglądarki
  odbiorców zablokują dostęp do kamery/mikrofonu. Do domknięcia: wykonanie
  kroków z `VPS.md` (w szczególności krok 6 -- Nginx + certyfikat).

## Podsumowanie

| Zrobione | Brakuje |
|---|---|
| 18 z 20 punktów | 2 z 20 punktów |

Brakujące punkty: **prośba uczestnika o mikrofon** (funkcja w ogóle nie
istnieje w kodzie) i **HTTPS** (gotowe w kodzie i w planie, czeka na
wdrożenie na VPS).

## Powiązane dokumenty

- `ARCHITEKTURA.md` -- jak to wszystko działa, w tym sekcja ETAP 8.
- `BEZPIECZENSTWO.md` -- pełny obraz zabezpieczeń.
- `VPS.md` -- plan wdrożenia produkcyjnego (w tym HTTPS).
- `INSTALACJA.md` -- lokalna instalacja deweloperska.
