Aktywni kontybutorzy:
- Oleksii Nawrocki - PM, Lead Fullstack Dev
- Tomasz Nowak - Backend Dev
- Julia Chmura - Frontend Dev
- Michał Domański - Frontend Dev
- Paweł Powęska - Frontend Dev
Byli kontrybutorzy:
- Jakub Czesnak - Frontend Dev
- Dawid Bajek - Fullstack Dev
- Mateusz Tokarz - Frontend Dev
Trippy to kompleksowa aplikacja mobilna (Android) oparta o architekturę klient-serwer, stworzona z myślą o osobach podróżujących w grupach. Rozwiązuje problem transparentnego zarządzania budżetem i sprawiedliwego rozliczania kosztów podczas wspólnych wyjazdów.
Zamiast żmudnego wpisywania wydatków do arkuszy kalkulacyjnych, Trippy oferuje intuicyjny interfejs, w którym uczestnicy mogą na bieżąco dodawać koszty, określać kto za nie zapłacił i kogo dotyczą. Zaawansowany silnik backendowy dba o bezpieczeństwo danych, przeliczanie bilansów i synchronizację między użytkownikami.
Projekt realizowany jest w architekturze mikrousługowej/monolitycznej z wykorzystaniem konteneryzacji, co ułatwia wdrożenie i utrzymanie spójnego środowiska deweloperskiego.
Architektura bazy danych opiera się na relacyjnym modelu (PostgreSQL) i została zaprojektowana w taki sposób, aby sprawnie zarządzać użytkownikami, wyjazdami oraz skomplikowanymi rozliczeniami. Składa się z czterech głównych obszarów:
- USER: Centralna tabela przechowująca dane kont, zabezpieczone hasła, statusy weryfikacji/blokady oraz preferencje (domyślna waluta).
- ROLE / CURRENCY / TRIP_ROLE: Tabele słownikowe standaryzujące uprawnienia w aplikacji, dostępne waluty oraz role pełnione podczas konkretnego wyjazdu (np. Organizator, Uczestnik).
- TRIP_EVENT: Reprezentuje konkretny wyjazd. Zawiera powiązanie z twórcą (
owner), nazwę, ramy czasowe, ustalony główny budżet oraz główną walutę wyjazdu. - TRIP_PARTICIPANTS: Tabela asocjacyjna łącząca wycieczkę z użytkownikami. Zarządza systemem zaproszeń (
isAccepted), indywidualnym budżetem uczestnika na danym wyjeździe oraz jego specjalną rolą (TRIP_ROLE).
- TRIP_NODE: Kluczowa tabela reprezentująca punkty na mapie wyjazdu, wydarzenia w harmonogramie lub konkretne transakcje. Każdy węzeł posiada czas trwania, koszt (
price), przypisanego autora (reporter) oraz flagęisSeparate, decydującą o tym, czy dany wydatek wchodzi w skład równego podziału kosztów.
- TRIP_POST: Wpisy (notatki, przemyślenia) dodawane przez uczestników do konkretnych węzłów wyjazdu.
- TRIP_PHOTO: Adresy URL zdjęć powiązane z konkretnymi wpisami, pozwalające na tworzenie wirtualnego albumu podróży.
- Frontend (Mobile): Android (Kotlin, Jetpack Compose)
- Backend: Java, Spring Boot, Spring Security
- Baza Danych: PostgreSQL, Flyway / Liquibase
- Infrastruktura: Docker, Docker Compose
flowchart LR
CLIENT["📱 Klient<br/>(Android / Web / Swagger)"]
SEC["🛡️ Spring Security<br/>JWT + CORS + BCrypt"]
subgraph API["🎯 Kontrolery REST /api/**"]
AUTH["🔐 /auth/** + /users/me"]
TRIP["✈️ /trips/** (events, participants,<br/>nodes, balances, invites)"]
SOC["📝 /posts/** + /photos/upload"]
DICT["📚 /dictionaries/**"]
ADM["👑 /admin/** (ADMIN only)"]
end
SVC["⚙️ Services<br/>(Auth, Trip, Node, Balance+Settlement,<br/>User, Photo, Jwt, Email)"]
REPO["💾 Spring Data JPA Repositories"]
DB[("🗄️ PostgreSQL<br/>Flyway V1..V5")]
FS[("📁 nginx /uploads")]
SMTP[("✉️ SMTP")]
CLIENT --> SEC --> API --> SVC --> REPO --> DB
SVC -. pliki .-> FS
SVC -. emaile .-> SMTP
classDef edge fill:#e8f5e9,stroke:#2e7d32;
classDef ctrl fill:#e3f2fd,stroke:#1565c0;
classDef svc fill:#fff3e0,stroke:#ef6c00;
classDef repo fill:#f3e5f5,stroke:#6a1b9a;
classDef ext fill:#fce4ec,stroke:#ad1457;
class SEC edge;
class AUTH,TRIP,SOC,DICT,ADM ctrl;
class SVC svc;
class REPO repo;
class DB,FS,SMTP ext;
- Resource-oriented routing — każdy zasób ma osobny kontroler, ścieżki są zagnieżdżone zgodnie z relacjami domenowymi (
/api/trips/{eventId}/nodes,/api/trips/{eventId}/participants). - Stateless JWT —
JwtAuthenticationFilterwaliduje token przy każdym żądaniu;TOKEN_BLACKLIST+REFRESH_TOKENSobsługują rotację i logout. - Separation of concerns — kontrolery są cienkie (walidacja DTO + mapowanie), logika żyje w
service/, dostęp do bazy wrepository/. - Strefa publiczna vs prywatna —
/api/auth/**,/swagger-ui/**,/v3/api-docs/**sąpermitAll(); reszta wymagaBearer <JWT>. Endpointy/api/admin/**dodatkowo wymagają roliADMIN. - Pliki poza aplikacją — uploady idą do dedykowanego serwera
nginx(port8888), backend trzyma tylko URL-e wTRIP_PHOTO/USERS.photo_url.
Celem MVP jest dostarczenie podstawowego przepływu pracy – od rejestracji, poprzez utworzenie wyjazdu i dodanie wydatków, aż po pokazanie prostego bilansu uczestników. Wersja ta zakłada równy podział kosztów i operowanie na jednej walucie.
Poniżej znajduje się specyfikacja głównych wymagań systemu w fazie MVP.
- Zarządzanie kontem (Autoryzacja): Użytkownik musi mieć możliwość rejestracji i logowania przy użyciu adresu email i hasła (zabezpieczone tokenem JWT).
- Tworzenie podróży (Trips): Zalogowany użytkownik może utworzyć nową podróż (podając jej nazwę oraz ramy czasowe), a także przeglądać listę swoich wyjazdów.
- Zarządzanie uczestnikami: Twórca podróży (Owner) ma możliwość dodawania innych zarejestrowanych w systemie użytkowników do danego wyjazdu.
- Ewidencja wydatków (Expenses): Każdy uczestnik podróży może dodać nowy wydatek, podając kwotę, tytuł wydatku (np. "Paliwo") oraz oznaczając osobę, która założyła pieniądze.
- Podgląd bilansu (Balances): System musi automatycznie obliczać i wyświetlać aktualny bilans dla każdego uczestnika (podział kosztów po równo), pokazując kto ma nadpłatę, a kto i ile jest winien grupie.
- Konteneryzacja środowiska: Cały backend (aplikacja Spring Boot oraz baza PostgreSQL) musi być uruchamiany lokalnie za pomocą jednego polecenia, wykorzystując plik
docker-compose.yml. - Bezpieczeństwo danych: Hasła użytkowników muszą być bezwzględnie haszowane w bazie danych (np. przy użyciu algorytmu BCrypt), a API chronione przed nieautoryzowanym dostępem.
- Responsywność i wydajność mobilna: Aplikacja kliencka na Androida musi działać płynnie, poprawnie obsługiwać stany ładowania/błędów sieciowych i być kompatybilna z urządzeniami z systemem Android 8.0 (API 26) lub nowszym.
W celu zapewnienia spójności, stabilności bazy kodu oraz ujednolicenia procesu integracji, zespół projektowy stosuje rygorystyczne zasady pracy z systemem Git. Proces oparty jest na zdefiniowanej hierarchii gałęzi (branches) oraz obowiązkowych przeglądach kodu (Code Review).
Architektura repozytorium opiera się na dwóch głównych gałęziach o nieograniczonym cyklu życia:
main– Gałąź produkcyjna. Zawiera wyłącznie kod przetestowany, stabilny i gotowy do wdrożenia (wersje wydań / MVP). Bezpośrednie wprowadzanie zmian (tzw. direct commit) do tej gałęzi jest surowo zabronione. Zmiany trafiają tu wyłącznie poprzez proces fuzji (merge).develop– Główna gałąź integracyjna. Agreguje zatwierdzone zmiany z gałęzi roboczych. Pełni funkcję bazy do tworzenia nowych odgałęzień i jest docelowym miejscem fuzji dla nowo zaimplementowanych funkcjonalności.
Każde nowe zadanie (funkcjonalność, poprawka, dokumentacja) wymaga utworzenia dedykowanej, tymczasowej gałęzi roboczej. Nazewnictwo musi być zgodne z formatem TR-XX-typ/krotki-opis-zadania, gdzie opis zapisany jest w formacie kebab-case (małe litery, słowa oddzielone myślnikiem). Natomiast na początku nazwy brancha należy umieścić numer ticketa (np. TR-01), żeby Jira była w stanie automatycznie zmieniać statusy ticketów w zależności od naszych interakcji.
Dopuszczalne prefiksy (typy):
TR-XX-feature/<nazwa>– Implementacja nowej funkcjonalności systemu (np.TR-01-feature/jwt-authentication,TR-01-feature/expense-entity). Gałąź tworzona zdevelop.TR-XX-bugfix/<nazwa>– Usunięcie błędu zidentyfikowanego w środowisku deweloperskim (np.TR-01-bugfix/balance-calculation-error). Gałąź tworzona zdevelop.TR-XX-hotfix/<nazwa>– Krytyczna poprawka błędu na środowisku produkcyjnym. Tworzona bezpośrednio z gałęzimain. Po zakończeniu i weryfikacji prac, gałąź jest włączana (merge) zarówno domain, jak i dodevelop(np.TR-01-hotfix/database-connection-loss).TR-XX-documentation/<nazwa>– Tworzenie, rozbudowa lub aktualizacja dokumentacji technicznej oraz plików konfiguracyjnych (np.TR-01-documentation/api-endpoints,TR-01-documentation/readme-update).TR-XX-refactor/<nazwa>– Restrukturyzacja i optymalizacja istniejącego kodu, niepociągająca za sobą zmian w jego obserwowalnym zachowaniu zewnętrznym (np.TR-01-refactor/user-service-architecture).
Wprowadzanie zmian do głównej linii kodu podlega ustandaryzowanemu procesowi:
- Synchronizacja: Pobranie aktualnego stanu gałęzi bazowej (
git pull origin develop). - Inicjalizacja: Utworzenie nowej gałęzi roboczej zgodnie z konwencją nazewnictwa (
git checkout -b <typ>/<nazwa>). - Implementacja: Wprowadzenie zmian, poprawne sformułowanie wiadomości commitów i ich lokalne zatwierdzenie.
- Publikacja: Przesłanie zmian do zdalnego repozytorium (
git push origin <typ>/<nazwa>). - Żądanie integracji: Utworzenie Pull Requesta (PR) do gałęzi docelowej (standardowo
develop). - Code Review: Obowiązkowa weryfikacja jakości i poprawności kodu przeprowadzona przez minimum jednego, innego członka zespołu.
- Fuzja (Merge): Włączenie kodu do gałęzi docelowej po uzyskaniu akceptacji roboczej (Approval) i pozytywnym przejściu ewentualnych testów zautomatyzowanych.
trippy/
|- backend/
|- mobile/
|- database/
|- .env
|- .gitignore
|- docker-compose.yml
Testy weryfikujące poprawność i bezpieczeństwo tworzenia nowych kont użytkowników.
| Nazwa Metody Testowej | Scenariusz (Given) | Akcja (When) | Oczekiwany Wynik (Then) |
|---|---|---|---|
shouldSuccessfullyRegisterUserAndReturnJwt |
Email jest wolny, hasło poprawne. | Wywołanie register(). |
Generuje JWT. Weryfikuje 1x zapis do DB, 1x zapis tokena i 1x wysyłkę emaila. |
shouldThrowExceptionWhenEmailIsAlreadyTaken |
Baza zwraca istniejącego użytkownika dla podanego emaila. | Wywołanie register(). |
Wyrzuca IllegalArgumentException. Udowadnia, że NIE wykonano zapisu do DB ani nie wysłano maila. |
shouldEnforceSecurityConstraintsOnNewUser |
Poprawne dane wejściowe. | Wywołanie register(). |
Przechwytuje (Captor) obiekt User. Sprawdza czy: hasło jest zaszyfrowane, rola to USER, a isVerified to false. |
shouldGenerateValidVerificationToken |
Poprawne dane wejściowe. | Wywołanie register(). |
Przechwytuje obiekt VerificationToken. Sprawdza, czy token nie jest pusty i ma poprawną datę wygaśnięcia. |
Testy weryfikujące proces uwierzytelniania, napisane w architekturze Fail-Fast.
| Nazwa Metody Testowej | Scenariusz (Given) | Akcja (When) | Oczekiwany Wynik (Then) |
|---|---|---|---|
shouldSuccessfullyAuthenticateAndReturnJwt |
Konto jest zweryfikowane (isVerified=true), poświadczenia są poprawne. |
Wywołanie authenticate(). |
Zwraca token JWT, weryfikuje poprawne odpytanie AuthenticationManager. |
shouldThrowExceptionWhenUserIsNotVerified |
Użytkownik podał dobre hasło, ale konto jest nieaktywne (isVerified=false). |
Wywołanie authenticate(). |
Wyrzuca RuntimeException. Weryfikuje, że JwtService NIGDY nie wydał tokena. |
shouldPropagateExceptionWhenBadCredentials |
AuthManager odrzuca hasło użytkownika. |
Wywołanie authenticate(). |
Wyrzuca BadCredentialsException. Weryfikuje, że w celach optymalizacji w ogóle NIE odpytano bazy danych. |
shouldThrowExceptionWhenUserNotFoundInDatabase |
Hasło przeszło, ale użytkownik nagle zniknął z bazy (Edge case). | Wywołanie authenticate(). |
Wyrzuca NoSuchElementException. Token nie zostaje wygenerowany. |
Testy algorytmów HMAC SHA-256 oraz obsługi JSON Web Tokens (izolowane od kontekstu Springa).
| Nazwa Metody Testowej | Scenariusz (Given) | Akcja (When) | Oczekiwany Wynik (Then) |
|---|---|---|---|
shouldGenerateTokenAndExtractUsername |
Ręcznie wstrzyknięty SecretKey i UserDetails. | Generowanie tokena. | Token nie jest nullem, a wyciągnięty Subject zgadza się z mailem. |
shouldReturnTrueWhenTokenIsValid |
Wygenerowany, świeży token dla danego usera. | Walidacja isTokenValid(). |
Zwraca wartość true. |
shouldReturnFalseWhenTokenBelongsToAnotherUser |
Token usera A sprawdzany na koncie usera B. | Walidacja isTokenValid(). |
Zwraca wartość false. |
shouldThrowExceptionWhenTokenIsExpired |
Czas życia tokena skrócony do 1ms. Wątek uśpiony na 10ms. | Walidacja isTokenValid(). |
Wyrzuca wyjątek ExpiredJwtException. |
Weryfikacja integracji z systemem SMTP przy pomocy zaślepek (Mocks).
| Nazwa Metody Testowej | Scenariusz (Given) | Akcja (When) | Oczekiwany Wynik (Then) |
|---|---|---|---|
shouldSendVerificationEmailWithCorrectContent |
Zdefiniowany adres email docelowy i UUID tokena. | Wywołanie sendVerificationEmail(). |
Przechwytuje obiekt SimpleMailMessage. Sprawdza poprawność nadawcy, odbiorcy, tytułu i gwarantuje obecność linku z tokenem w ciele wiadomości. |
Weryfikacja logiki aktywacji konta przez endpointy REST API.
| Nazwa Metody Testowej | Scenariusz (Given) | Akcja (When) | Oczekiwany Wynik (Then) |
|---|---|---|---|
shouldSuccessfullyVerifyEmail |
W bazie istnieje token, jego data ważności jest poprawna. | GET /api/auth/verify?token=... |
Zwraca HTTP 200 (OK). Flaga użytkownika zmienia się na isVerified=true. Token jest usuwany z bazy. |
shouldReturnBadRequestWhenTokenIsExpired |
W bazie istnieje token, ale jego data wygasła. | GET /api/auth/verify?token=... |
Zwraca HTTP 400 (Bad Request). Flaga użytkownika to wciąż false. |
shouldThrowExceptionWhenTokenDoesNotExist |
Podano zmyślony/błędny token. | GET /api/auth/verify?token=fake |
Wyrzuca RuntimeException ("Nieprawidłowy token"). |
Moduł rozliczeń odpowiada na pytanie "kto komu ile jest winien po wycieczce?" i zwraca zminimalizowaną listę przelewów, które wyrównają wszystkich uczestników do zera. Implementacja: service/trip/TripBalanceService (orchestrator) + service/trip/DebtSettlementService (czysty algorytm).
GET /api/trips/{tripId}/balances
Authorization: Bearer <JWT>
Zwraca: łączną kwotę wydatków współdzielonych, netto-bilans każdego zaakceptowanego uczestnika oraz listę przelewów. Pełen kontrakt: Swagger UI (/swagger-ui.html), tag Trip Balances.
- W rozliczeniu uczestniczą tylko zaakceptowani uczestnicy (
isAccepted=true). Zaproszeni, ale nieaktywowani są pomijani. - Wydatki z flagą
isSeparate=truesą wyłączone z wspólnego budżetu (reporter zapłacił "za siebie"). - Reszta wydatków dzielona jest po równo między zaakceptowanych uczestników, z deterministyczną dystrybucją groszy resztkowych (np. 100 zł / 3 osoby → pierwsi sortowani po
userIddostają +0.01). - Bilanse liczone są na żywo — każde wywołanie odzwierciedla aktualny stan wydatków w DB. Wycieczki nie są obecnie "zamykane" (patrz Future work).
- Dostęp tylko dla zaakceptowanych uczestników wycieczki — caller spoza listy dostaje
403.
Hybrydowy, zwraca wynik z mniejszą liczbą transakcji:
- Greedy (Simplified Debt Graph) — zawsze. Max-heap kredytorów, min-heap dłużników, pary największy-z-największym. Złożoność O(N log N), gwarancja ≤ N−1 transakcji.
- DP + bitmask (Optimal Account Balancing) — tylko gdy N ≤ 15. Znajduje partycję bilansów na maksymalną liczbę zero-sum subsetów; minimum transakcji = N − liczba_subsetów. Złożoność O(3^N · N) — dla N=15 to ~14M operacji, < 100 ms.
Próg N=15 wynika z eksplozji 3^N: dla 16+ używany jest wyłącznie greedy. W praktyce grupy turystyczne nie przekraczają kilkunastu osób, więc DP odpala się w praktycznie każdym realnym przypadku.
Pełna dokumentacja techniczna (model matematyczny, dowód optymalności, worked example, benchmarki): docs/Trippy-Algorytm-Rozliczen.docx.
Obecnie bilanse są zawsze "live". W kolejnej iteracji planujemy ręczne zamykanie wycieczki przez ownera:
- nowy stan
TripEvent.status = CLOSED+closedAttimestamp - po zamknięciu kalkulacja jest zamrożona (snapshot w nowej tabeli
TripSettlementSnapshot) - nowe wydatki wymagają jawnego "re-open" przez ownera
- frontend pokazuje status zamknięcia + datę
To pozwoli uniknąć sytuacji "ktoś dorzucił 50 zł po rozliczeniu i wszystko się rozjechało".
Całe środowisko (backend + PostgreSQL + pgAdmin + frontend + serwer plików nginx) uruchamiane jest przez docker compose z katalogu głównego repozytorium. Poniższa instrukcja pokrywa pierwszy start, codzienną pracę, restart oraz przełączanie między profilem deweloperskim a produkcyjnym.
| Wymaganie | Wersja / Uwaga |
|---|---|
| Docker Engine | 24+ |
| Docker Compose | v2 (wbudowane w Docker Desktop / docker compose, nie docker-compose) |
| Wolne porty na hoście | 5432 (Postgres), 5050 (pgAdmin), 8080 (backend), 5173 (frontend), 8888 (serwer plików) |
Plik .env |
Obecny w roocie repo — tworzony raz z .env.example |
# 1. Skopiuj szablon zmiennych środowiskowych
cp .env.example .env
# 2. (Opcjonalnie) Uzupełnij sekrety SMTP w .env — MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD.
# Na profilu dev nie są wymagane (emaile trafiają do logs/emails/).
# 3. Zbuduj obrazy i uruchom stack w tle
docker compose up -d --build
# 4. Zweryfikuj że wszystkie kontenery wstały
docker compose psPo pierwszym starcie Flyway wykonuje migracje, a DataInitializer (tylko na profilu dev) zasiewa bazę kontami testowymi (patrz sekcja Dane Dostępowe).
| Cel | Komenda |
|---|---|
| Start całego stacku w tle | docker compose up -d |
| Start konkretnego serwisu | docker compose up -d backend |
| Stop bez usuwania kontenerów | docker compose stop |
| Stop i usuń kontenery (volume'y zostają — dane w bazie nietknięte) | docker compose down |
| Pokaż status kontenerów | docker compose ps |
| Logi on-line z backendu | docker compose logs -f backend |
| Logi on-line z wszystkich | docker compose logs -f |
| Wejdź do shella w kontenerze backendu | docker compose exec backend sh |
Różne poziomy „restartu" — od najtańszego do najcięższego:
| Scenariusz | Komenda |
|---|---|
Zmiana w application*.properties lub .env → wystarczy restart kontenera |
docker compose restart backend |
Zmiana w kodzie Java (src/**/*.java) → DevTools hot-reload robi to automatycznie |
— (tylko zapisz plik) |
Dodany nowy dependency w pom.xml lub zmiana Dockerfile → rebuild obrazu |
docker compose up -d --build backend |
| Pełny rebuild całego stacku | docker compose up -d --build |
Aktywny profil Spring Boot sterowany jest zmienną SPRING_PROFILES_ACTIVE w pliku .env:
# W .env
SPRING_PROFILES_ACTIVE=dev # lokalne środowisko (domyślnie)
SPRING_PROFILES_ACTIVE=prod # produkcyjne zachowanie (strict CORS, Mailtrap SMTP, seedery wyłączone)Różnice między profilami:
| Obszar | dev |
prod |
|---|---|---|
| CORS | Dowolny origin (allowedOriginPatterns: *) |
Tylko app.frontend.url |
| Wysyłka emaili | LocalFileEmailService — zapis do backend/logs/emails/ |
MailtrapEmailService — realny SMTP |
Seedery (DataInitializer, UserSeeder, ...) |
Aktywne na pustej bazie | Wyłączone (+ runtime guard SeederGuardConfig) |
| DevTools hot-reload | Tak | Nie |
Po zmianie profilu wymagany jest restart:
docker compose restart backend
# lub pełny rebuild jeśli właśnie zmieniłeś też kod:
docker compose up -d --build backendDataInitializer uruchamia seedery tylko gdy baza jest pusta. Żeby wywołać cały proces od nowa, trzeba usunąć wolumen Postgresa:
# UWAGA: -v usuwa wolumeny = kasuje CAŁĄ bazę oraz wszystkie uploady plików.
docker compose down -v
docker compose up -d --build| Serwis | URL / port |
|---|---|
| Backend API | http://localhost:8080 |
| Swagger UI | http://localhost:8080/swagger-ui.html |
| Frontend (Vite dev server) | http://localhost:5173 |
| Serwer plików (nginx) | http://localhost:8888/uploads/ |
| pgAdmin | http://localhost:5050 (login z PGADMIN_EMAIL / PGADMIN_PASSWORD z .env) |
| PostgreSQL | localhost:5432 (creds z DB_USER / DB_PASSWORD) |
| Problem | Przyczyna / rozwiązanie |
|---|---|
mvn clean wyrzuca Failed to delete /app/target: Resource busy |
Katalog ./backend/target jest zbind-mountowany do kontenera. Buduj z flagą pomijającą clean (./mvnw test zamiast ./mvnw clean test) albo tymczasowo wyłącz kontener backendu (docker compose stop backend) przed buildem lokalnym. |
Port 5432 / 8080 / 5173 zajęty |
Zatrzymaj lokalnego Postgresa / inną appkę, albo zmień mapowanie portu w docker-compose.yml. |
Backend nie startuje, log: UnknownHostException: db |
Zapomnij o docker compose up bez db — backend depend-suje na zdrowym Postgresie. Odpal docker compose up -d (bez nazwy serwisu) żeby podnieść zależności. |
Zmieniłem SPRING_PROFILES_ACTIVE w .env, ale appka dalej działa po staremu |
env_file jest ładowany przy starcie kontenera — potrzebny docker compose restart backend (sam stop + start nie przeładowuje env w niektórych wersjach Compose). |
Emaile weryfikacyjne nie docierają na profilu dev |
Są zapisywane do backend/logs/emails/*.eml jako pliki. Otwórz plik, skopiuj link weryfikacyjny, wklej w przeglądarce. |
Kontener trippy_backend w pętli restartu |
Zobacz docker compose logs backend — najczęściej brak migracji Flyway, nieaktualny .env lub brakujący JWT_SECRET_KEY. |
Pełna, interaktywna dokumentacja REST API generowana jest automatycznie przez springdoc-openapi i dostępna po uruchomieniu backendu:
| Zasób | URL |
|---|---|
| Swagger UI (interaktywny) | http://localhost:8080/swagger-ui.html |
| Specyfikacja OpenAPI (JSON) | http://localhost:8080/v3/api-docs |
Oba adresy są publiczne (nie wymagają logowania) — skonfigurowane jako permitAll()
w SecurityConfig.
Większość endpointów wymaga tokena JWT. Aby testować je z poziomu Swagger UI:
- Wykonaj
POST /api/auth/loginz danymi konta testowego (patrz Dane Dostępowe). W odpowiedzi otrzymaszaccessToken. - Kliknij przycisk Authorize (kłódka w prawym górnym rogu Swagger UI).
- Wklej sam token (springdoc sam dokleja prefiks
Bearer— nie wpisuj go ręcznie). - Zatwierdź. Od tej pory wszystkie żądania z UI niosą nagłówek
Authorization: Bearer <token>.
Schemat bezpieczeństwa zdefiniowany jest globalnie w OpenApiConfig
(bearerAuth, typ HTTP, schemat bearer, format JWT) i nałożony globalnie przez
addSecurityItem(...), dzięki czemu kłódka pojawia się przy każdym endpoincie.
Endpointy publiczne (/api/auth/**, zasoby Swaggera) są dodatkowo oznaczone tak,
by nie wymagały tokena.
Wydatek (dawny Expense) nie jest już osobnym zasobem — to zwykły węzeł
(TripNode) z dodatnią ceną i flagą isSeparate. Endpointy /api/expenses
zostały usunięte. Operacje na wydatkach realizuje się przez:
| Operacja | Endpoint |
|---|---|
| Utworzenie wydatku/węzła | POST /api/trips/{eventId}/nodes |
| Lista węzłów/wydatków wycieczki | GET /api/trips/{eventId}/nodes |
| Pojedynczy węzeł | GET /api/trips/{eventId}/nodes/{nodeId} |
| Edycja (pełna podmiana) | PUT /api/trips/{eventId}/nodes/{nodeId} |
| Usunięcie | DELETE /api/trips/{eventId}/nodes/{nodeId} |
Pola: dawne title → name; dochodzą wymagane startTime i endTime.
Proces automatycznego wypełniania bazy danych opiera się na klasie centralnej oraz dedykowanych skryptach.
| Komponent / Krok | Akcja (Zasada działania) | Znaczenie / Cel |
|---|---|---|
Punkt wejściowy (DataInitializer) |
Weryfikuje przy starcie aplikacji, czy baza danych jest pusta (sprawdza liczbę rekordów w tabeli users). |
Zapobiega dublowaniu danych. Skrypty uruchamiają się wyłącznie na "czystej" bazie. |
| Podział odpowiedzialności | Uruchamia sekwencyjnie dedykowane klasy *Seeder (np. UserSeeder, CurrencySeeder, TripEventSeeder). |
Utrzymanie czystości kodu – każda klasa odpowiada wyłącznie za jedną tabelę. |
| Zachowanie Kolejności | Najpierw ładowane są słowniki (waluty, role) i użytkownicy, a następnie wycieczki, posty i punkty planu. | Gwarantuje zachowanie integralności relacyjnej bazy danych i poprawne przypisanie kluczy obcych (Foreign Keys). |
Mechanizm jest w pełni skalowalny. W zależności od potrzeb, dodawanie danych opiera się na poniższych scenariuszach:
| Typ Rozszerzenia | Wymagane Kroki | Kontekst / Przykład |
|---|---|---|
| Dodanie nowych rekordów do istniejącej tabeli | Edycja odpowiedniej klasy w pakiecie com.navrotskyi.trippyapi.seeder. |
Chcąc dodać nową walutę (np. JPY), wystarczy dopisać nowy obiekt do listy wewnątrz klasy CurrencySeeder. |
| Dodanie nowej tabeli (Krok 1: Utworzenie) | Stworzenie nowej klasy *Seeder i oznaczenie jej jako komponent Springa. |
Tworzymy np. klasę TripReviewSeeder z własnymi danymi startowymi. |
| Dodanie nowej tabeli (Krok 2: Wstrzyknięcie) | Wstrzyknięcie nowo utworzonego Seedera do klasy centralnej DataInitializer. |
Dodanie repozytorium oraz seedera przez Dependency Injection (konstruktor). |
| Dodanie nowej tabeli (Krok 3: Wywołanie) | Wywołanie seedera wewnątrz metody run() w DataInitializer. |
Kluczowe: Wywołanie musi nastąpić po zapisaniu danych, od których zależy nowa tabela (np. posty dopiero po zapisaniu użytkowników i wycieczek). |
Aby wywołać proces seedowania od zera (na czystej bazie), zobacz sekcję Reset bazy danych w instrukcji uruchomienia. Kluczowe: DataInitializer jest aktywny tylko na profilu dev i tylko gdy baza jest pusta — reset wymaga usunięcia wolumenu Postgresa przez docker compose down -v.
Poniższe dane są generowane automatycznie przez UserSeeder. Wszystkie konta mają przypisane to samo hasło testowe.
| Rola w systemie | Adres E-mail | Hasło | Uprawnienia (Systemowe) |
|---|---|---|---|
| Administrator | admin@trippy.pl |
password |
Pełny dostęp (ADMIN) |
| Użytkownik 1 | jan@trippy.pl |
password |
Standardowe (USER) |
- Przed wysłaniem plików nie będących obrazami.
- Przed przekroczeniem 5MB rozmiaru jednego pliku.
- Przed wysłaniem ukrytego złośliwego programu pod dozwolonymi rozszerzeniami.
- Przed indeksacją plików.
- Przed spamem zdjęć (limit rate)
- Wyciek prywatniści: Należy wycinać metadane z plików.
- Całkowity limit dyskowy dla użytkownika: ograniczenie ilości pamięci dla każdego użytkownika.
- WAF/CDN: Podpięcie domeny pod darmową wersję CloudFlare.
- Wstaw podany link do ścieżki i ustaw metodę
POST.
http://localhost:8080/api/photos/upload
- W Body ustaw typ na
form-data. - Dodaj nową wartość, nazwij ją
file. - Wstaw w value z eksploratora plików dowolny plik.
- Kliknij
Send.
