930 lines
34 KiB
Markdown
930 lines
34 KiB
Markdown
# Platforma webinarowa
|
|
|
|
Platforma do organizacji szkolen i webinarow. Osobna instancja dla kazdego klienta,
|
|
roznice miedzy instancjami wylacznie w konfiguracji.
|
|
|
|
Backend obsluguje konfiguracje instancji, wysylke e-mail i SMS, webinary, zapisy
|
|
uczestnikow oraz uwierzytelnianie JWT. Frontend jest na razie strona startowa
|
|
sprawdzajaca polaczenie z backendem.
|
|
|
|
## Uruchomienie lokalne
|
|
|
|
Wymagany Docker z wtyczka Compose. Nic poza tym nie jest potrzebne, Maven i Node
|
|
dzialaja wewnatrz obrazow.
|
|
|
|
### 1. Przygotuj konfiguracje
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Wartosci domyslne wystarczaja do uruchomienia lokalnego. Przed wdrozeniem u klienta
|
|
ustaw wlasne `DB_PASSWORD`, `JWT_SECRET` i klucze MinIO.
|
|
|
|
### 2. Zbuduj i uruchom kontenery
|
|
|
|
```bash
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Pierwszy build pobiera zaleznosci Mavena i npm, wiec trwa kilka minut. Kolejne
|
|
korzystaja z cache.
|
|
|
|
### 3. Sprawdz, czy dziala
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/health
|
|
```
|
|
|
|
Oczekiwana odpowiedz:
|
|
|
|
```json
|
|
{"status":"UP","version":"0.1.0"}
|
|
```
|
|
|
|
Aplikacja jest dostepna pod <http://localhost:8081>. Strona startowa pokazuje status
|
|
i wersje backendu pobrane przez `/api/health`.
|
|
|
|
## Dostepne adresy
|
|
|
|
| Usluga | Adres | Uwagi |
|
|
| --- | --- | --- |
|
|
| Frontend | <http://localhost:8081> | nginx serwuje SPA i proxuje `/api` |
|
|
| Backend | <http://localhost:8080/api> | bezposrednio, z pominieciem nginx |
|
|
| Swagger UI | <http://localhost:8080/api/swagger-ui.html> | tylko przy `SWAGGER_ENABLED=true` |
|
|
| Konsola MinIO | <http://localhost:9001> | login z `MINIO_ACCESS_KEY` |
|
|
| PostgreSQL | `localhost:5432` | dane z `.env` |
|
|
|
|
Porty zmieniasz zmiennymi `*_PORT_EXPOSED` w `.env`.
|
|
|
|
## Strona publiczna
|
|
|
|
Czesc dostepna bez logowania, dla uczestnikow.
|
|
|
|
| Ekran | Sciezka |
|
|
| --- | --- |
|
|
| Strona webinaru z formularzem zapisu | `/w/{slug}` |
|
|
| Wlasny zapis, kalendarz i rezygnacja | `/zapis/{token}` |
|
|
|
|
Adres do rozeslania uczestnikom to `APP_PUBLIC_URL` z ustawien plus `/w/{slug}`,
|
|
na przyklad <http://localhost:8081/w/zamowienia-publiczne-2026>.
|
|
|
|
Formularz jest ukrywany, gdy zapisy nie przyjmuja zgloszen. Uczestnik widzi wtedy
|
|
powod: brak miejsc, miniety termin, zapisy zamkniete recznie, webinar odwolany albo
|
|
webinar juz sie odbyl. Wersje robocze nie sa dostepne publicznie.
|
|
|
|
## Adresy: korzen nalezy do goscia, panel ma wlasny prefiks
|
|
|
|
Pod <http://localhost:8081> jest **publiczna strona glowna Akademii**, dostepna bez
|
|
logowania. Panel administracyjny siedzi pod `/panel`.
|
|
|
|
### Publiczne, bez logowania
|
|
|
|
| Ekran | Sciezka |
|
|
| --- | --- |
|
|
| Strona glowna Akademii | `/` |
|
|
| Kursy, webinary, cennik | `/kursy`, `/webinary`, `/cennik` |
|
|
| Strona kursu | `/kurs/{slug}` |
|
|
| Strona zapisu na webinar | `/w/{slug}` |
|
|
| Weryfikacja certyfikatu | `/certyfikat/{numer}` |
|
|
| Logowanie (wspolne) | `/logowanie` |
|
|
|
|
### Konta kursantow
|
|
|
|
Rejestracja i odzyskiwanie hasla dotycza **wylacznie kont uczestnikow**. Konta panelu
|
|
zaklada administrator instancji.
|
|
|
|
| Ekran | Sciezka |
|
|
| --- | --- |
|
|
| Rejestracja | `/rejestracja` |
|
|
| Przypomnienie hasla | `/przypomnienie-hasla` |
|
|
| Ustawienie nowego hasla | `/reset-hasla/{token}` |
|
|
| Potwierdzenie adresu | `/potwierdzenie/{token}` |
|
|
| Pulpit kursanta | `/moje-konto` |
|
|
|
|
### Panel administracyjny
|
|
|
|
| Ekran | Sciezka | Dostep |
|
|
| --- | --- | --- |
|
|
| Pulpit | `/panel` | ADMIN, MODERATOR |
|
|
| Webinary | `/panel/webinary` | ADMIN, MODERATOR |
|
|
| Formularz webinaru | `/panel/webinary/nowy`, `/panel/webinary/{id}/edycja` | ADMIN, MODERATOR |
|
|
| Zapisani | `/panel/webinary/{id}/zapisy` | ADMIN, MODERATOR |
|
|
| Kursy | `/panel/kursy` | ADMIN, MODERATOR |
|
|
| Ustawienia | `/panel/ustawienia` | tylko ADMIN |
|
|
| Uzytkownicy | `/panel/uzytkownicy` | tylko ADMIN |
|
|
|
|
Stare adresy panelu (`/ustawienia`, `/uzytkownicy`, `/kontakty`, `/zapytania`,
|
|
`/zamowienia`, `/banery`, `/pulpit`) oraz stare adresy Akademii (`/akademia/*`)
|
|
**przekierowuja na nowe**, wiec zapisane zakladki dzialaja dalej.
|
|
|
|
**Wyjatek: `/kursy` i `/webinary` nie przekierowuja.** Oba adresy naleza teraz do
|
|
publicznych list, bo sa krotkie i trafiaja do wyszukiwarek oraz materialow
|
|
reklamowych. Kolizje rozstrzygnieto na korzysc czesci publicznej, swiadomie
|
|
godzac sie na to, ze administrator z zapisana zakladka trafi na oferte. Lagodzi
|
|
to pasek pokazywany na obu listach **wylacznie osobom z rola ADMIN lub
|
|
MODERATOR**: mowi, ze panel jest pod `/panel`, prowadzi tam odnosnikiem i znika
|
|
na stale po zamknieciu.
|
|
|
|
### Jedno logowanie dla obu swiatow
|
|
|
|
Formularz pod `/logowanie` jest wspolny. Backend (`POST /api/auth/login/unified`)
|
|
sprawdza haslo **osobno** w kontach panelu i w kontach kursantow, po czym odpowiada,
|
|
dokad kierowac: front niczego nie zgaduje.
|
|
|
|
Hasla obu swiatow pozostaja **niezalezne**. Token panelu powstaje wylacznie wtedy, gdy
|
|
haslo pasowalo do konta panelu, wiec wyciek hasla kursanta nadal nie otwiera panelu.
|
|
Gdy to samo haslo pasuje w obu miejscach, uzytkownik dostaje wybor, dokad wejsc.
|
|
Dotychczasowe `POST /api/auth/login` i `POST /api/academy/login` **dzialaja bez zmian**.
|
|
|
|
Terminy sa prezentowane w strefie czasowej webinaru, zawsze z jawnym oznaczeniem strefy.
|
|
|
|
## Logowanie
|
|
|
|
Endpointy `/api/admin/**` wymagaja tokenu JWT. Pierwsze konto administratora powstaje
|
|
przy pierwszym starcie z `ADMIN_EMAIL` i `ADMIN_PASSWORD` z pliku `.env`.
|
|
|
|
Gdy `ADMIN_PASSWORD` jest puste, haslo jest losowane i wypisane **jeden raz** w logu:
|
|
|
|
```bash
|
|
docker compose logs backend | grep -A6 "UTWORZONO PIERWSZE KONTO"
|
|
```
|
|
|
|
Logowanie:
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"admin@localhost","password":"twoje-haslo"}'
|
|
```
|
|
|
|
Token z odpowiedzi doklada sie do kolejnych zadan:
|
|
|
|
```bash
|
|
TOKEN="wklej-token"
|
|
curl http://localhost:8080/api/admin/webinars -H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
Zmiana wlasnego hasla:
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/auth/change-password \
|
|
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
-d '{"currentPassword":"stare","newPassword":"nowe-haslo-min-10-znakow"}'
|
|
```
|
|
|
|
### Role
|
|
|
|
| Sciezka | ADMIN | MODERATOR | bez tokenu |
|
|
| --- | --- | --- | --- |
|
|
| `/api/health`, `/api/public/**` | tak | tak | tak |
|
|
| `/api/admin/webinars/**` | tak | tak | 401 |
|
|
| `/api/admin/settings/**` | tak | 403 | 401 |
|
|
| `/api/admin/users/**` | tak | 403 | 401 |
|
|
|
|
### Blokada po nieudanych logowaniach
|
|
|
|
Po 5 nieudanych probach dla adresu e-mail (albo 20 dla adresu IP) logowanie jest
|
|
odrzucane przez 15 minut, takze przy poprawnym hasle. Odpowiedz jest taka sama jak
|
|
przy zlym hasle. Liczniki sa w pamieci i znikaja po restarcie kontenera.
|
|
|
|
## Konfiguracja instancji przez API
|
|
|
|
Ustawienia trzymane sa w tabeli `app_settings` i maja pierwszenstwo przed zmiennymi
|
|
srodowiskowymi. Zmiana dziala od razu, bez restartu aplikacji.
|
|
|
|
Lista ustawien:
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/admin/settings -H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
Wszystkie ponizsze wywolania wymagaja tokenu administratora, patrz sekcja Logowanie.
|
|
|
|
Wartosci kluczy typu PASSWORD nigdy nie wracaja w odpowiedzi. Zamiast wartosci jest flaga
|
|
`valueSet`, ktora mowi czy haslo jest ustawione.
|
|
|
|
### Ustawienie konta SMTP
|
|
|
|
```bash
|
|
curl -X PUT http://localhost:8080/api/admin/settings \
|
|
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
-d '{
|
|
"MAIL_HOST": "pzators.nazwa.pl",
|
|
"MAIL_PORT": "465",
|
|
"MAIL_SSL_ENABLED": "true",
|
|
"MAIL_USERNAME": "powiadomienia@twojadomena.pl",
|
|
"MAIL_PASSWORD": "haslo-do-konta",
|
|
"MAIL_FROM_NAME": "Nazwa nadawcy",
|
|
"APP_BRAND_NAME": "Nazwa marki",
|
|
"APP_PUBLIC_URL": "https://webinar.twojadomena.pl"
|
|
}'
|
|
```
|
|
|
|
Puste pole przy `MAIL_PASSWORD` oznacza "nie zmieniaj" i nie kasuje zapisanego hasla.
|
|
|
|
### Test wysylki e-mail
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/admin/settings/test-email \
|
|
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
-d '{"recipient":"twoj@adres.pl"}'
|
|
```
|
|
|
|
Odpowiedz zawiera `success` oraz komunikat. Przy bledzie jest to pelna tresc bledu z serwera
|
|
SMTP, na przyklad `535 5.7.0 Authentication error`.
|
|
|
|
### Bramka SMS
|
|
|
|
```bash
|
|
curl -X PUT http://localhost:8080/api/admin/settings \
|
|
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
-d '{
|
|
"SMS_ENABLED": "true",
|
|
"SMS_API_KEY": "klucz-api-bramki",
|
|
"SMS_CREATOR": "Webinar"
|
|
}'
|
|
|
|
curl -X POST http://localhost:8080/api/admin/settings/test-sms \
|
|
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
-d '{"recipient":"600100200"}'
|
|
```
|
|
|
|
## Wdrozenie na serwerze klienta
|
|
|
|
Kazdy klient dostaje wlasna instancje: wlasny serwer, wlasna domena, wlasna baza.
|
|
Kod jest ten sam, roznice wylacznie w konfiguracji.
|
|
|
|
### Co jest po stronie klienta, a co po naszej
|
|
|
|
Aplikacja nasluchuje **po HTTP, wewnatrz sieci Dockera**, na petli lokalnej serwera
|
|
(domyslnie `127.0.0.1:8081`). Nie obsluguje SSL i nie jest wystawiona bezposrednio
|
|
do internetu.
|
|
|
|
**Certyfikat SSL i reverse proxy sa po stronie serwera klienta.** Administrator
|
|
serwera musi postawic przed aplikacja nginx, Caddy, Traefik albo inny serwer,
|
|
ktory zakonczy polaczenie HTTPS i przekaze ruch na port aplikacji. Bez tego
|
|
aplikacja nie bedzie dostepna z zewnatrz, i tak ma byc.
|
|
|
|
Przykladowa konfiguracja nginx po stronie klienta:
|
|
|
|
```nginx
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name webinary.klient.pl;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/webinary.klient.pl/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/webinary.klient.pl/privkey.pem;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8081;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
# aplikacja czyta adres uczestnika z tego naglowka, wiec musi byc ustawiony
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
# naglowek User-Agent jest potrzebny do rozpoznania robotow komunikatorow
|
|
proxy_set_header User-Agent $http_user_agent;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
server_name webinary.klient.pl;
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
```
|
|
|
|
### Krok 1: przygotowanie serwera
|
|
|
|
Wymagany Docker z wtyczka Compose. Sprawdz:
|
|
|
|
```bash
|
|
docker --version
|
|
docker compose version
|
|
```
|
|
|
|
Sklonuj repozytorium i wejdz do katalogu projektu:
|
|
|
|
```bash
|
|
git clone <adres-repozytorium> webinar-platform
|
|
cd webinar-platform
|
|
```
|
|
|
|
### Krok 2: konfiguracja instancji
|
|
|
|
```bash
|
|
cp .env.production.example .env.production
|
|
chmod 600 .env.production
|
|
```
|
|
|
|
Wygeneruj sekrety i wpisz je do `.env.production`:
|
|
|
|
```bash
|
|
# sekret JWT, minimum 32 znaki. Aplikacja nie wystartuje bez niego.
|
|
openssl rand -base64 48
|
|
|
|
# haslo bazy danych
|
|
openssl rand -base64 24
|
|
|
|
# klucz tajny MinIO
|
|
openssl rand -base64 24
|
|
```
|
|
|
|
Uzupelnij w `.env.production`:
|
|
|
|
| Zmienna | Wartosc |
|
|
| --- | --- |
|
|
| `JWT_SECRET` | wynik `openssl rand -base64 48` |
|
|
| `DB_PASSWORD` | wynik `openssl rand -base64 24` |
|
|
| `MINIO_ACCESS_KEY` | dowolny login, np. `webinar-minio` |
|
|
| `MINIO_SECRET_KEY` | wynik `openssl rand -base64 24` |
|
|
| `ADMIN_EMAIL` | adres pierwszego administratora |
|
|
| `ADMIN_PASSWORD` | zostaw **puste**, haslo zostanie wylosowane |
|
|
| `APP_PUBLIC_URL` | `https://webinary.klient.pl`, bez ukosnika na koncu |
|
|
| `CORS_ALLOWED_ORIGINS` | to samo co `APP_PUBLIC_URL` |
|
|
|
|
`ADMIN_PASSWORD` lepiej zostawic puste: haslo zostanie wygenerowane losowo
|
|
i pokazane raz w logu, wiec nie bedzie lezalo w pliku na dysku.
|
|
|
|
Konfiguracji SMTP i bramki SMS **nie ma w tym pliku**. Ustawia sie ja w panelu
|
|
po uruchomieniu, w kroku 5.
|
|
|
|
### Krok 3: sprawdzenie konfiguracji
|
|
|
|
Przed pierwszym uruchomieniem:
|
|
|
|
```bash
|
|
./scripts/check-config.sh --skip-db
|
|
```
|
|
|
|
Skrypt **odmawia startu** (kod wyjscia 1), gdy `JWT_SECRET` jest pusty, krotszy
|
|
niz 32 znaki albo rowny wartosci przykladowej, gdy `ADMIN_PASSWORD` jest rowne
|
|
przykladowej, albo gdy `APP_PUBLIC_URL` jest pusty lub wskazuje na `localhost`.
|
|
Kazdy z tych przypadkow kosztuje na produkcji wiecej niz nieudany start:
|
|
przykladowy sekret oznacza, ze **kazdy, kto widzial repozytorium, moze podrobic
|
|
token administratora**, a adres lokalny oznacza martwe linki w kazdym mailu.
|
|
|
|
Po uruchomieniu warto powtorzyc bez `--skip-db`: skrypt odpyta wtedy instancje
|
|
o klucze wymagane, ktore ustawia sie w panelu (SMTP, bramka SMS, dostawca wideo).
|
|
Lista wymaganych kluczy pochodzi z katalogu `SettingKey` w backendzie, wiec nie
|
|
rozjedzie sie z kodem przy dodaniu nowego ustawienia.
|
|
|
|
### Krok 4: uruchomienie
|
|
|
|
```bash
|
|
docker compose -f docker-compose.prod.yml --env-file .env.production up -d --build
|
|
```
|
|
|
|
Pierwszy build pobiera zaleznosci Mavena i npm, wiec trwa kilka minut.
|
|
|
|
Sprawdz, czy wszystkie kontenery dzialaja:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.prod.yml --env-file .env.production ps
|
|
```
|
|
|
|
### Krok 5: haslo administratora i pierwsze logowanie
|
|
|
|
Jesli `ADMIN_PASSWORD` bylo puste, odczytaj wygenerowane haslo z logu:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.prod.yml --env-file .env.production \
|
|
logs backend | grep -A6 "UTWORZONO PIERWSZE KONTO"
|
|
```
|
|
|
|
Haslo jest pokazane **tylko raz**. Zapisz je w menedzerze hasel, zaloguj sie
|
|
do panelu pod `https://webinary.klient.pl/logowanie` i zmien je od razu:
|
|
Ustawienia konta, albo `POST /api/auth/change-password`.
|
|
|
|
### Krok 6: konfiguracja poczty i bramki SMS przez panel
|
|
|
|
SMTP i bramka SMS konfigurowane sa **wylacznie w panelu**, nie w pliku `.env`.
|
|
Wartosci trafiaja do bazy, wiec zmiana dziala od razu, bez restartu kontenera,
|
|
a klient moze poprawic je samodzielnie.
|
|
|
|
1. Zaloguj sie do panelu i wejdz w **Ustawienia**.
|
|
2. Uzupelnij sekcje **Konto e-mail (SMTP)**:
|
|
- `MAIL_HOST` serwer poczty, na przyklad `smtp.domena.pl`
|
|
- `MAIL_PORT` 465 dla SSL albo 587 dla STARTTLS
|
|
- `MAIL_SSL_ENABLED` wlaczone dla portu 465
|
|
- `MAIL_USERNAME` pelny adres e-mail nadawcy
|
|
- `MAIL_PASSWORD` haslo do konta
|
|
- `MAIL_FROM_NAME` nazwa widoczna dla odbiorcy
|
|
3. Kliknij **Zapisz ustawienia**.
|
|
4. W bloku **Test konfiguracji** pod ta sekcja wpisz swoj adres i kliknij **Wyslij**.
|
|
Przy powodzeniu zobaczysz potwierdzenie, przy bledzie pelna tresc odpowiedzi
|
|
serwera SMTP, na przyklad `535 5.7.0 Authentication error`.
|
|
5. Sprawdz, czy wiadomosc dotarla, takze do folderu ze spamem.
|
|
6. Analogicznie uzupelnij sekcje **Bramka SMS** i przetestuj ja przyciskiem
|
|
**Wyslij** w bloku testu.
|
|
|
|
Haslo SMTP i klucz API bramki sa sekretami: nigdy nie wracaja w odpowiedzi API,
|
|
a puste pole przy zapisie oznacza brak zmiany, nie skasowanie wartosci.
|
|
|
|
### Krok 7: weryfikacja wdrozenia
|
|
|
|
Pelny test odbiorczy, punkt po punkcie, jest w osobnym pliku:
|
|
**[DEPLOYMENT_CHECKLIST.md](DEPLOYMENT_CHECKLIST.md)**. Obejmuje obie sciezki
|
|
(webinarowa i Akademii) oraz to, czego nie da sie sprawdzic lokalnie: notyfikacje
|
|
ITN Autopay, nagrywanie przez Egress, webhook dostawcy wideo i zajawki
|
|
w komunikatorach.
|
|
|
|
Skrocone sprawdzenie:
|
|
|
|
```bash
|
|
# aplikacja odpowiada wewnatrz serwera
|
|
curl http://127.0.0.1:8081/api/health
|
|
|
|
# aplikacja odpowiada przez reverse proxy, z zewnatrz
|
|
curl https://webinary.klient.pl/api/health
|
|
```
|
|
|
|
Oczekiwana odpowiedz w obu przypadkach:
|
|
|
|
```json
|
|
{"status":"UP","version":"0.1.0"}
|
|
```
|
|
|
|
Sprawdz jeszcze:
|
|
|
|
- panel otwiera sie pod `https://webinary.klient.pl/logowanie` i logowanie dziala,
|
|
- baza i MinIO **nie** sa dostepne z zewnatrz:
|
|
`nc -zv webinary.klient.pl 5432` oraz `nc -zv webinary.klient.pl 9000`
|
|
powinny odmowic polaczenia,
|
|
- utworz probny webinar, opublikuj go i wejdz na `/w/{slug}` z telefonu,
|
|
- zapisz sie na probny webinar i sprawdz, czy przyszedl mail z zalacznikiem
|
|
`invite.ics`,
|
|
- usun probny webinar przed przekazaniem instancji klientowi.
|
|
|
|
### Aktualizacja instancji
|
|
|
|
```bash
|
|
git pull
|
|
docker compose -f docker-compose.prod.yml --env-file .env.production up -d --build
|
|
```
|
|
|
|
Migracje Flyway wykonuja sie automatycznie przy starcie backendu. Przed
|
|
aktualizacja wykonaj kopie zapasowa, patrz sekcja nizej.
|
|
|
|
### Zatrzymanie i restart
|
|
|
|
```bash
|
|
# restart pojedynczej uslugi
|
|
docker compose -f docker-compose.prod.yml --env-file .env.production restart backend
|
|
|
|
# zatrzymanie calosci, dane w wolumenach zostaja
|
|
docker compose -f docker-compose.prod.yml --env-file .env.production down
|
|
```
|
|
|
|
Nie uzywaj `down -v` na produkcji: ta komenda usuwa wolumeny wraz z danymi.
|
|
|
|
## Kopia zapasowa i odtworzenie
|
|
|
|
### Wykonanie kopii
|
|
|
|
```bash
|
|
./scripts/backup.sh
|
|
```
|
|
|
|
Kopia trafia do `./backups/RRRRMMDD-GGMMSS/` i zawiera:
|
|
|
|
| Plik | Zawartosc |
|
|
| --- | --- |
|
|
| `baza.dump` | zrzut PostgreSQL w formacie custom |
|
|
| `minio.tar.gz` | zawartosc wolumenu MinIO |
|
|
| `env.production.kopia` | konfiguracja instancji, **zawiera sekrety** |
|
|
| `INFO.txt` | data, nazwa serwera, instrukcja odtworzenia |
|
|
|
|
Katalog docelowy mozna podac jako argument:
|
|
|
|
```bash
|
|
./scripts/backup.sh /mnt/backup/webinar
|
|
```
|
|
|
|
Skrypt zachowuje 14 ostatnich kopii i usuwa starsze. Zmien zmienna `KEEP_LAST`,
|
|
gdy potrzebujesz innej retencji.
|
|
|
|
### Kopia automatyczna
|
|
|
|
Wpis do crona, codziennie o 3:00:
|
|
|
|
```bash
|
|
crontab -e
|
|
```
|
|
|
|
```
|
|
0 3 * * * cd /opt/webinar-platform && ./scripts/backup.sh /mnt/backup/webinar >> /var/log/webinar-backup.log 2>&1
|
|
```
|
|
|
|
Kopie zawieraja sekrety, wiec katalog docelowy musi miec ograniczone uprawnienia,
|
|
a jesli trafiaja poza serwer, powinny byc szyfrowane.
|
|
|
|
#### Ile miejsca potrzeba
|
|
|
|
Rozmiar kopii to prawie w calosci **nagrania**: baza nawet przy kilku tysiacach
|
|
zapisow rzadko przekracza 100 MB, a jedno nagranie godzinnego webinaru w 720p
|
|
wazy 400 MB do 1 GB.
|
|
|
|
| Skladnik | Rozmiar jednej kopii |
|
|
| --- | --- |
|
|
| Baza (`baza.dump`, format custom) | 5-100 MB |
|
|
| Materialy i obrazki w tresci | 50-500 MB |
|
|
| Certyfikaty PDF | okolo 200 kB na sztuke |
|
|
| **Nagrania** | **400 MB - 1 GB na godzine nagrania** |
|
|
|
|
Wzor na miejsce potrzebne przy domyslnym `KEEP_LAST=14`:
|
|
|
|
```
|
|
miejsce = (rozmiar wolumenu MinIO + rozmiar bazy) x 14
|
|
```
|
|
|
|
Przyklad: instancja z 20 godzinami nagran zajmuje okolo 12 GB, wiec 14 kopii
|
|
wymaga **okolo 170 GB**. Przy takiej skali warto:
|
|
|
|
- zmniejszyc `KEEP_LAST` do 7 i trzymac kopie tygodniowe osobno,
|
|
- albo wylaczyc nagrania z kopii codziennej i archiwizowac je rzadziej: pliki
|
|
w MinIO sa **niezmienne po wgraniu**, wiec kopia sprzed tygodnia zawiera
|
|
dokladnie te same nagrania co dzisiejsza.
|
|
|
|
**Sprawdzenie biezacego rozmiaru:**
|
|
|
|
```bash
|
|
# wolumen MinIO, czyli nagrania, materialy, certyfikaty i obrazki
|
|
docker system df -v | grep minio-data
|
|
|
|
# baza
|
|
docker compose -f docker-compose.prod.yml exec -T postgres \
|
|
psql -U webinar -d webinar -c "SELECT pg_size_pretty(pg_database_size('webinar'));"
|
|
```
|
|
|
|
**Kopia bez nagran**, gdy miejsce jest ograniczone: zrzut samej bazy wystarczy
|
|
do odtworzenia calej konfiguracji, wszystkich zapisow, kursow i wynikow.
|
|
Zginie wtedy wylacznie **tresc plikow**, a wpisy w bazie zostana i beda
|
|
wskazywac na nieistniejace obiekty.
|
|
|
|
```
|
|
0 3 * * * cd /opt/webinar-platform && SKIP_MINIO=1 ./scripts/backup.sh >> /var/log/webinar-backup.log 2>&1
|
|
0 4 * * 0 cd /opt/webinar-platform && ./scripts/backup.sh /mnt/backup/webinar-pelna >> /var/log/webinar-backup.log 2>&1
|
|
```
|
|
|
|
Pierwszy wpis: codziennie sama baza. Drugi: raz w tygodniu calosc.
|
|
|
|
### Odtworzenie
|
|
|
|
```bash
|
|
./scripts/restore.sh ./backups/20260828-143000
|
|
```
|
|
|
|
Skrypt pokazuje metryczke kopii i wymaga wpisania `ODTWORZ`, zeby kontynuowac.
|
|
Nastepnie zatrzymuje backend, odtwarza baze i pliki MinIO, i uruchamia backend
|
|
z powrotem.
|
|
|
|
**Odtworzenie nadpisuje biezace dane.** Przed odtworzeniem wykonaj kopie stanu
|
|
biezacego, zeby dalo sie wrocic:
|
|
|
|
```bash
|
|
./scripts/backup.sh
|
|
./scripts/restore.sh ./backups/20260828-143000
|
|
```
|
|
|
|
Po odtworzeniu sprawdz:
|
|
|
|
```bash
|
|
curl http://127.0.0.1:8081/api/health
|
|
```
|
|
|
|
oraz zaloguj sie do panelu i obejrzyj liste webinarow i zapisow.
|
|
|
|
### Odtworzenie na nowym serwerze
|
|
|
|
1. Zainstaluj Dockera, sklonuj repozytorium.
|
|
2. Skopiuj `env.production.kopia` z kopii jako `.env.production` i ustaw
|
|
`chmod 600`. Zachowaj **ten sam** `JWT_SECRET`, inaczej wszyscy zostana
|
|
wylogowani, oraz to samo `DB_PASSWORD`, bo odtwarzana baza go uzywa.
|
|
3. Uruchom uslugi: `docker compose -f docker-compose.prod.yml --env-file .env.production up -d`.
|
|
4. Odtworz dane: `./scripts/restore.sh <katalog-kopii>`.
|
|
5. Przekieruj domene na nowy serwer i sprawdz `/api/health`.
|
|
|
|
## Wideo: wybor dostawcy
|
|
|
|
Platforma nie jest przywiazana do jednego dostawcy. Wybor jest na dwoch
|
|
poziomach: ustawienie instancji (`Ustawienia > Wideo`) i opcjonalny wybor przy
|
|
pojedynczym webinarze, ktory ma pierwszenstwo.
|
|
|
|
### Co gwarantuje ktory dostawca
|
|
|
|
| Dostawca | Uprawnienia | Nagrywanie | Wymaga |
|
|
| --- | --- | --- | --- |
|
|
| **LIVEKIT** | egzekwuje **serwer**, na podstawie podpisanego tokenu | automatyczne, prosto do MinIO | wlasny serwer albo LiveKit Cloud |
|
|
| **JITSI** z JWT | serwer egzekwuje **tylko role moderatora** | reczne wgranie pliku | wlasny serwer Jitsi z JWT |
|
|
| **JITSI** bez JWT | **tylko interfejs**, pokoj otwarty dla kazdego | reczne wgranie pliku | nic, dziala na `meet.jit.si` |
|
|
| **DAILY** | egzekwuje serwer | pobierane od dostawcy | konto Daily.co |
|
|
| **NONE** | brak, organizator wkleja wlasny link | reczne wgranie pliku | nic |
|
|
|
|
**Roznica miedzy LiveKit a Jitsi bez JWT jest zasadnicza, nie kosmetyczna.**
|
|
Przy LiveKit uczestnik bez prawa publikacji nie wlaczy kamery, bo serwer odrzuci
|
|
strumien. Przy Jitsi bez JWT ukrycie przycisku kamery jest jedynym, co go
|
|
powstrzymuje, a wystarczy wejsc wlasnym klientem, zeby to obejsc.
|
|
|
|
### Wlasny serwer LiveKit
|
|
|
|
Kontenery sa w profilu `livekit`, wylaczonym domyslnie: instancja w trybie NONE
|
|
albo na Jitsi nie uruchamia niczego zbednego.
|
|
|
|
```bash
|
|
# uzupelnij w .env: LIVEKIT_API_KEY, LIVEKIT_API_SECRET, TURN_PASSWORD
|
|
docker compose --profile livekit up -d
|
|
```
|
|
|
|
Ten sam klucz i sekret wpisz w panelu (`Ustawienia > Wideo`): tutaj konfiguruja
|
|
serwer, w panelu sluza do podpisywania tokenow wejscia.
|
|
|
|
**Porty do otwarcia na firewallu:**
|
|
|
|
| Port | Protokol | Do czego |
|
|
| --- | --- | --- |
|
|
| 7880 | TCP | sygnalizacja WebSocket i API serwera |
|
|
| 7881 | TCP | media po TCP, droga zapasowa gdy UDP jest zablokowane |
|
|
| 50000-50100 | **UDP** | **glowna droga audio i wideo** |
|
|
| 49160-49200 | UDP | TURN, dla uczestnikow za restrykcyjnym NAT-em |
|
|
|
|
**Zakres UDP jest obowiazkowy.** Bez niego uczestnicy dolacza do pokoju i zobacza
|
|
liste obecnych, ale nie beda sie slyszec ani widziec. To najczestsza przyczyna
|
|
zgloszenia "wideo nie dziala" przy poprawnie skonfigurowanym serwerze.
|
|
|
|
TURN (`coturn`) dziala w `network_mode: host`, bo musi widziec prawdziwe adresy
|
|
IP: za NAT-em Dockera rozdawalby uczestnikom adresy wewnetrzne kontenera.
|
|
|
|
### Pulapki uruchomieniowe LiveKit
|
|
|
|
Zebrane przy uruchomieniu profilu na zywo. **Kazda z nich blokuje pierwsze
|
|
wdrozenie**, a zadna nie daje czytelnego komunikatu.
|
|
|
|
**1. Zakres portow UDP musi byc wolny w systemie.**
|
|
Windows rezerwuje zakresy dla Hyper-V i WSL (sprawdzenie:
|
|
`netsh interface ipv4 show excludedportrange protocol=udp`). Domyslne
|
|
50000-50100 wpada w rezerwacje 50000-50059 i kontener nie wstaje z bledem
|
|
"ports are not available". Zmiana `LIVEKIT_UDP_RANGE` w `.env` wystarcza:
|
|
zakres idzie do serwera zmienna `UDP_PORT` i **musi byc taki sam po obu
|
|
stronach mapowania portow**, inaczej Docker odrzuca konfiguracje.
|
|
|
|
**2. `use_external_ip: true` nadpisuje `NODE_IP`.**
|
|
Przy uruchomieniu lokalnym serwer wykrywa publiczny adres lacza i rozglasza go
|
|
uczestnikom, mimo ustawionego `NODE_IP=127.0.0.1`. Przegladarka na tej samej
|
|
maszynie probuje sie tam polaczyc i nie zestawia mediow. Objaw jest mylacy:
|
|
**uczestnik wchodzi do pokoju i widzi liste obecnych, ale nikogo nie slyszy
|
|
i nie widzi**. Do testow lokalnych trzeba ustawic `use_external_ip: false`
|
|
w `livekit/livekit.yaml`.
|
|
|
|
**3. Adres serwera to dwie rozne wartosci.**
|
|
`LIVEKIT_URL` opisuje adres widziany przez **przegladarke** (`ws://localhost:7880`
|
|
lokalnie). Backend w kontenerze trafilby pod nim sam w siebie, wiec do wywolan
|
|
API i Egress sluzy osobne `LIVEKIT_INTERNAL_URL` (`http://livekit:7880`).
|
|
Bez tego nagrywanie konczy sie bledem "Failed to connect to localhost:7880"
|
|
przy poprawnie dzialajacym serwerze.
|
|
|
|
**4. Wersja serwera musi nadazac za `livekit-client`.**
|
|
Klient 2.22 laczy sie sciezka `/rtc/v1`, ktorej serwer 1.8 nie zna: przegladarka
|
|
dostaje 404 i komunikat "v1 RTC path not found. Consider upgrading your LiveKit
|
|
server version". Sala wyglada na dzialajaca, ale nie plynie ani obraz, ani
|
|
dzwiek. Obraz jest przypiety do `v1.13`; **podnoszac bibliotekę we froncie,
|
|
sprawdz wersje serwera**.
|
|
|
|
**5. Nagrywanie wymaga Egress i Redisa.**
|
|
Sam serwer LiveKit nie nagrywa. Egress dostaje zadanie **przez Redisa**, wiec
|
|
bez niego serwer przyjmuje zadanie, nie ma go komu przekazac, a nagrywanie
|
|
konczy sie timeoutem. Oba kontenery sa w profilu `livekit`.
|
|
|
|
**6. Egress uruchamia prawdziwa przegladarke.**
|
|
Stad `cap_add: SYS_ADMIN` (piaskownica Chrome) i `shm_size: 1gb` (domyslne
|
|
64 MB nie wystarcza i przegladarka wysypuje sie w polowie nagrania).
|
|
|
|
**7. Nie uzywaj `network_mode: host` dla Egress na Docker Desktop.**
|
|
Na Windows i macOS kontenery chodza w maszynie wirtualnej, wiec tryb host laczy
|
|
kontener z siecia **tej maszyny**, a nie z hostem: kontener widzi wtedy adres
|
|
`192.168.65.x`, a nie adres systemu. Skutek jest widoczny dopiero w logach
|
|
serwera: kandydaci ICE Egress maja **puste adresy IP** (`udp host :41557`),
|
|
serwer odrzuca je jako `[filtered]`, a polaczenie mediow przechodzi
|
|
`connected -> disconnected -> failed`. Nagranie konczy sie wtedy bledem
|
|
"Start signal not received", ktory nie ma z sygnalem nic wspolnego.
|
|
|
|
Egress ma zostac w zwyklej sieci Compose. **`network_mode: bridge` tez jest
|
|
bledem**: to domyslna siec Dockera, a nie siec Compose, wiec kontener przestaje
|
|
widziec `redis` i `livekit` po nazwie (`lookup redis: no such host`).
|
|
|
|
**8. Egress otwiera strone szablonu.**
|
|
Domyslnie pobiera ja z `https://template.livekit.io`, czyli z internetu:
|
|
**instancja w sieci zamknietej nie nagra niczego**. Dlatego wskazujemy wlasny
|
|
szablon (`LIVEKIT_TEMPLATE_URL`, domyslnie `http://frontend/nagrywanie`),
|
|
serwowany z naszego frontu.
|
|
|
|
### Stan weryfikacji nagrywania
|
|
|
|
**Nagrywanie przez Egress nie zostalo sprawdzone end-to-end.** Zweryfikowane
|
|
zostalo wszystko, co do niego prowadzi, ale nie powstal plik.
|
|
|
|
Co **dziala** i jest potwierdzone na zywo:
|
|
|
|
- spotkania: obraz i dzwiek plyna miedzy uczestnikami (zmierzone przyrostem
|
|
bajtow w statystykach WebRTC odbiorcy),
|
|
- uprawnienia egzekwuje serwer: uczestnik z rola SPEAKER opublikowal audio,
|
|
a **tej samej probie dla wideo serwer nie nadal zadnego wpisu o sciezce**,
|
|
- Egress laczy sie z Redisem (`service ready`), przyjmuje zadanie, dolacza do
|
|
pokoju i **zestawia polaczenie mediow po UDP** z prawidlowym adresem,
|
|
- szablon nagrywania jest pobierany i wykonywany: jego JavaScript laczy sie
|
|
z pokojem jako uczestnik `EG_*`.
|
|
|
|
Gdzie proces sie **zatrzymuje**: szablon nie wola `window.START_RECORDING()`,
|
|
bo w chwili nagrywania **nie zasubskrybowal zadnej sciezki**. Egress przerywa
|
|
wtedy nagranie z kodem 412 "Start signal not received".
|
|
|
|
Przyczyna po stronie srodowiska, nie kodu: zeby to domknac, potrzebny jest
|
|
uczestnik faktycznie publikujacy obraz **w tej samej sieci co Egress**.
|
|
W srodowisku testowym na Windows serwer rozglasza jeden adres (`NODE_IP`),
|
|
wiec albo dziala przegladarka na hoscie, albo kontenery w sieci Dockera, ale
|
|
nie oba naraz. Na Linuksie, gdzie kontenery i przegladarka widza ten sam
|
|
adres, ten podzial nie wystepuje.
|
|
|
|
**Funkcjonalnie nic to nie blokuje**: reczne wgranie pliku nagrania w zakladce
|
|
"Po webinarze" dziala i pozostaje droga awaryjna dla kazdego dostawcy.
|
|
Przed pierwszym wdrozeniem z automatycznym nagrywaniem nalezy te sciezke
|
|
przejsc na docelowym serwerze.
|
|
|
|
### Jitsi
|
|
|
|
Zakladamy serwer **zewnetrzny**, wskazywany domena w `Ustawienia > Wideo`.
|
|
Nie ma go w `docker-compose.yml`: wlasny serwer Jitsi to kilkanascie uslug
|
|
i osobne wdrozenie, a `meet.jit.si` dziala bez konfiguracji.
|
|
|
|
Nagrywanie Jitsi wymaga komponentu Jibri, ktorego nie zakladamy: to osobna
|
|
usluga z wlasna przegladarka i wlasnym zuzyciem CPU na kazde nagranie. Panel
|
|
podpowiada w tym przypadku reczne wgranie pliku w zakladce "Po webinarze".
|
|
|
|
## Platnosci Autopay
|
|
|
|
Kursy platne rozlicza Autopay (dawniej BlueMedia). Integracja obejmuje
|
|
przedtransakcje, notyfikacje ITN i nadanie dostepu do kursu.
|
|
|
|
### Czego NIE DA SIE sprawdzic na localhost
|
|
|
|
**ITN wymaga publicznej domeny z waznym certyfikatem.** Autopay sam laczy sie
|
|
z naszym serwerem, wiec `localhost` jest dla niego nieosiagalny. Sprawdzone
|
|
lokalnie zostalo wszystko, co dzieje sie **po** odebraniu komunikatu:
|
|
|
|
| Element | Jak sprawdzony |
|
|
| --- | --- |
|
|
| Skrot na przykladzie z dokumentacji | `AutopayHashTest`, 11 testow |
|
|
| Pomijanie pustych pol wraz z separatorem | `AutopayHashTest` |
|
|
| Parsowanie ITN i odpornosc na XXE | `AutopayItnTest`, 11 testow |
|
|
| Weryfikacja podpisu, kwoty i waluty | test przeplywu na instancji |
|
|
| Idempotencja i FAILURE po SUCCESS | test przeplywu na instancji |
|
|
|
|
### Konfiguracja przed testem
|
|
|
|
Ustawienia w panelu, `Ustawienia -> Aplikacja`:
|
|
|
|
| Klucz | Wartosc na czas testow |
|
|
| --- | --- |
|
|
| `AUTOPAY_ENABLED` | `true` |
|
|
| `AUTOPAY_MODE` | `TEST` |
|
|
| `AUTOPAY_SERVICE_ID` | identyfikator z panelu Autopay |
|
|
| `AUTOPAY_SHARED_KEY` | klucz wspoldzielony z panelu Autopay |
|
|
| `AUTOPAY_HASH_ALGORITHM` | zgodny z ustawieniem po stronie operatora |
|
|
| `AUTOPAY_ITN_ALLOWED_IPS` | puste, dopoki nie znasz aktualnych adresow |
|
|
|
|
**Adresy srodowisk sa zaszyte w kodzie i nie sa ustawieniem:**
|
|
`https://testpay.autopay.eu` dla testow, `https://pay.autopay.eu` dla produkcji.
|
|
Pomylka oznaczalaby przyjmowanie prawdziwych pieniedzy na srodowisku testowym
|
|
albo transakcje testowe na produkcji, ktore nigdy sie nie rozlicza.
|
|
|
|
W panelu Autopay trzeba wskazac adres notyfikacji:
|
|
|
|
```
|
|
https://TWOJA-DOMENA/api/webhooks/autopay/itn
|
|
```
|
|
|
|
### Co przetestowac po wdrozeniu
|
|
|
|
Kolejnosc ma znaczenie: kazdy kolejny punkt zaklada, ze poprzedni przeszedl.
|
|
|
|
1. **Przedtransakcja odpowiada.** Kup kurs platny jako kursant. Powinienes
|
|
trafic na bramke Autopay, a nie zobaczyc bledu. W logu backendu nie moze byc
|
|
ostrzezenia `brak linku kontynuacji`. Zamowienie dostaje status `PENDING`.
|
|
2. **Blad konfiguracji jest czytelny.** Wpisz zly `AUTOPAY_SERVICE_ID`
|
|
i sprobuj kupic: kursant ma zobaczyc komunikat o niedostepnosci platnosci,
|
|
a zamowienie zostac w `NEW`, gotowe do ponowienia na tym samym numerze.
|
|
W logu pojawi sie `SERVICE_NOT_FOUND`.
|
|
3. **Platnosc udana nadaje dostep.** Zaplac w srodowisku testowym operatora.
|
|
Po powrocie strona pokazuje oczekiwanie na potwierdzenie, a dostep pojawia
|
|
sie dopiero **po komunikacie ITN**. W panelu, na karcie zamowienia, ma byc
|
|
widoczny komunikat `SUCCESS` z oznaczeniem `podpis poprawny`
|
|
i `przetworzony`.
|
|
4. **Powrot z bramki niczego nie nadaje.** Wejdz recznie na adres powrotu bez
|
|
platnosci. Dostepu ma nie byc: adres powrotu to zwykle przekierowanie
|
|
przegladarki, ktore da sie wywolac samemu.
|
|
5. **Platnosc nieudana nie nadaje dostepu.** Powtorz zakup i wybierz w bramce
|
|
scenariusz niepowodzenia. Zamowienie ma dostac `FAILED`, dostepu ma nie byc.
|
|
6. **FAILURE po SUCCESS nie odbiera dostepu.** To najwazniejszy przypadek:
|
|
zaplac skutecznie, a potem doprowadz do komunikatu `FAILURE` z **innym**
|
|
`remoteID`. Dostep ma **zostac**. Dokumentacja operatora opisuje ten
|
|
przypadek wprost, a jego zlamanie zabiera kurs osobie, ktora zaplacila.
|
|
7. **Powtorzona notyfikacja nie dubluje niczego.** Autopay ponawia komunikat
|
|
az do potwierdzenia. Po kilku powtorkach ma byc **jeden** zapis na kurs
|
|
i jeden mail, a w dzienniku kilka wpisow, z ktorych tylko pierwszy ma
|
|
`przetworzony`.
|
|
8. **Kwota niezgodna jest odrzucana.** Jesli operator udostepnia narzedzie do
|
|
recznego wyslania ITN, wyslij komunikat z inna kwota niz zamowiona: ma dostac
|
|
`NOTCONFIRMED`, a dostepu ma nie byc.
|
|
9. **Odpowiedz jest poprawnie podpisana.** Operator odrzuci potwierdzenie
|
|
z bledny skrotem i bedzie ponawial notyfikacje. Objawem jest rosnaca liczba
|
|
wpisow w dzienniku dla tego samego zamowienia.
|
|
|
|
### Scenariusze testowe operatora
|
|
|
|
Dane kart i przelewow testowych sa w dokumentacji Autopay:
|
|
|
|
- Dokumentacja techniczna: <https://developers.autopay.pl/>
|
|
- Panel srodowiska testowego: <https://testpay.autopay.eu/>
|
|
|
|
Scenariusze udanej i nieudanej platnosci, wraz z danymi testowymi, operator
|
|
udostepnia w panelu po zalozeniu konta testowego. **Nie zgadywac ich**: numery
|
|
kart testowych zmieniaja sie miedzy wersjami uslugi.
|
|
|
|
### Gdzie szukac przy problemie
|
|
|
|
- **Karta zamowienia w panelu** (`Akademia -> Zamowienia`) pokazuje pelny
|
|
dziennik komunikatow ITN wraz z surowa trescia i informacja, czy podpis sie
|
|
zgadzal. Przy sporze o platnosc to jedyny dowod.
|
|
- **Komunikat odrzucony tez jest zapisany**, z oznaczeniem `podpis niepoprawny`.
|
|
Brak wpisu oznacza, ze notyfikacja w ogole nie dotarla: sprawdz adres
|
|
w panelu operatora i czy domena ma wazny certyfikat.
|
|
- `docker compose logs -f backend | grep -i autopay`
|
|
|
|
### Reczne nadanie dostepu
|
|
|
|
Przy platnosci przelewem tradycyjnym: karta zamowienia, przycisk
|
|
**Nadaj dostep recznie**. Notatka jest wymagana, bo bez niej po miesiacu nie
|
|
da sie ustalic, czy pieniadze wplynely i kto to sprawdzil.
|
|
|
|
## Testy
|
|
|
|
Testy integracyjne wymagaja dzialajacego kontenera postgres, bo sprawdzaja blokade
|
|
wiersza w prawdziwej bazie.
|
|
|
|
```bash
|
|
docker compose up -d postgres
|
|
|
|
docker run --rm --network webinar-platform_default \
|
|
-e TEST_DB_HOST=postgres \
|
|
-v "$(pwd)/backend:/b" -v webinar-m2:/root/.m2 -w /b \
|
|
maven:3.9-eclipse-temurin-21 mvn -B test
|
|
```
|
|
|
|
Najwazniejszy z nich, `RegistrationConcurrencyTest`, uruchamia 40 rownoczesnych zapisow
|
|
na webinar z limitem 10 miejsc i sprawdza, ze w bazie powstalo dokladnie 10 rekordow.
|
|
|
|
## Zatrzymanie
|
|
|
|
```bash
|
|
docker compose down
|
|
```
|
|
|
|
Dane w wolumenach zostaja zachowane. Aby usunac takze dane:
|
|
|
|
```bash
|
|
docker compose down -v
|
|
```
|
|
|
|
## Praca nad frontendem bez Dockera
|
|
|
|
Serwer deweloperski Vite z hot reloadem, przy backendzie dzialajacym w Dockerze:
|
|
|
|
```bash
|
|
cd frontend
|
|
npm install
|
|
npm run dev
|
|
```
|
|
|
|
Frontend startuje na <http://localhost:5173>. Aby odpytywal backend bezposrednio,
|
|
utworz `frontend/.env.local`:
|
|
|
|
```
|
|
VITE_API_URL=http://localhost:8080/api
|
|
```
|
|
|
|
Origin `http://localhost:5173` jest juz na liscie `CORS_ALLOWED_ORIGINS`.
|
|
|
|
## Struktura
|
|
|
|
```
|
|
backend/ Spring Boot, pakiet com.softspm.webinar
|
|
frontend/ React + TypeScript + Vite, nginx w obrazie produkcyjnym
|
|
docker-compose.yml
|
|
.env.example
|
|
```
|
|
|
|
Migracje bazy trafiaja do `backend/src/main/resources/db/migration` jako `Vn__opis.sql`.
|
|
Schemat tworzy wylacznie Flyway, Hibernate go tylko weryfikuje (`ddl-auto: validate`).
|
|
|
|
Test automatycznego deployu. |