YLEVAADE.md
34,063 bytes
| 1 | # Projekti ülevaade — SplitApp (Reisikulude jagaja) |
|---|---|
| 2 | |
| 3 | See dokument kirjeldab tervet süsteemi: **frontendi** (Vue 3) ja **backendi** (ASP.NET Core 10) ning seda, kuidas need omavahel suhtlevad. |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## 1. Mis on SplitApp? |
| 8 | |
| 9 | SplitApp on reisiplaneerimise ja ühiste kulude jagamise rakendus. Kasutajad saavad: |
| 10 | |
| 11 | - luua reise ja kutsuda kaaslasi |
| 12 | - sisestada ühiseid kulusid ja jagada neid nelja erineva meetodi järgi |
| 13 | - hallata eelarvekategooriaid ja jälgida kulutusi |
| 14 | - pidada soovinimekirja tegevustest/kohtadest koos hääletusega |
| 15 | - teha grupiotsuseid küsitluste kaudu |
| 16 | - lõpuks arvestada, kes kellele võlgu on (arveldused) |
| 17 | |
| 18 | Süsteem koosneb kahest eraldiseisvast projektist, mis suhtlevad REST API kaudu: |
| 19 | |
| 20 | | Projekt | Kaust | Tehnoloogia | |
| 21 | |---|---|---| |
| 22 | | **Frontend** | `rasmju-js-a7` | Vue 3 + TypeScript + Vite | |
| 23 | | **Backend** | `rasmju-csweb-phase3` | ASP.NET Core 10 + PostgreSQL | |
| 24 | |
| 25 | --- |
| 26 | |
| 27 | ## 2. Frontend — `rasmju-js-a7` |
| 28 | |
| 29 | ### 2.1 Tehnoloogiad |
| 30 | |
| 31 | - **Vue 3.5** (Composition API) + **TypeScript 6** |
| 32 | - **Vite 8** — build tool ja arendusserver |
| 33 | - **Vue Router 5** — lehekülgede ruutimine |
| 34 | - **Pinia 3** — olekuhaldus |
| 35 | - **Axios 1.14** — HTTP klient |
| 36 | - **Bootstrap 5.3** — stiilide raamistik |
| 37 | - **Vitest 4** — unit testid |
| 38 | - **ESLint + Oxlint + Prettier** — koodi kvaliteet |
| 39 | |
| 40 | ### 2.2 Projekti struktuur |
| 41 | |
| 42 | ``` |
| 43 | src/ |
| 44 | ├── components/ # Jagatud komponendid |
| 45 | │ ├── SplitMethodSelector.vue # Kulu jagamise UI (4 meetodit) |
| 46 | │ └── ToastContainer.vue # Teavituste kuvamine |
| 47 | ├── composables/ |
| 48 | │ └── useToast.ts # Globaalne teavituste süsteem |
| 49 | ├── directives/ |
| 50 | │ └── vAnimate.ts # Scroll-animatsiooni direktiiv |
| 51 | ├── router/ |
| 52 | │ └── index.ts # Kõik ruudid + autentimise valvur |
| 53 | ├── services/ # API kliendid |
| 54 | │ ├── httpClient.ts # Axiose seadistus + interceptorid |
| 55 | │ ├── AccountService.ts # Login, register, refresh, logout |
| 56 | │ ├── TripService.ts # Reisid |
| 57 | │ ├── ExpenseService.ts # Kulud |
| 58 | │ ├── BudgetCategoryService.ts # Eelarvekategooriad |
| 59 | │ ├── CurrencyService.ts # Valuutad |
| 60 | │ ├── PollService.ts # Küsitlused |
| 61 | │ ├── WishlistService.ts # Soovinimekiri |
| 62 | │ ├── SettlementService.ts # Arveldused |
| 63 | │ └── InvitationService.ts # Kutsed |
| 64 | ├── stores/ |
| 65 | │ └── auth.ts # JWT + refreshToken + userName |
| 66 | ├── types/ # TypeScript liidesed (DTO vastavalt API-le) |
| 67 | ├── utils/ |
| 68 | │ ├── formatCurrency.ts # Valuuta vormindus |
| 69 | │ └── parseJwt.ts # JWT dekodeerimine |
| 70 | ├── views/ # Leheküljed |
| 71 | │ ├── HomeView.vue |
| 72 | │ ├── LoginView.vue |
| 73 | │ ├── RegisterView.vue |
| 74 | │ ├── trips/ # IndexView, DetailView, CreateView, EditView |
| 75 | │ ├── expenses/ # Index, Create, Edit |
| 76 | │ ├── budget-categories/ # Index, Create, Edit |
| 77 | │ ├── wishlist/ # Index, Create, Edit |
| 78 | │ ├── polls/ # Index, Create, Detail |
| 79 | │ ├── invitations/ # AcceptView |
| 80 | │ ├── members/ # MembersView |
| 81 | │ └── settlements/ # SettlementView |
| 82 | ├── App.vue # Juurkomponent (navbar + router-view) |
| 83 | └── main.ts # Käivituspunkt |
| 84 | ``` |
| 85 | |
| 86 | ### 2.3 Ruutimine |
| 87 | |
| 88 | Kogu rakendus kasutab **pesastatud ruuteid** — reis on parent-route, mille all asuvad kõik reisispetsiifilised vaated. `trips/DetailView.vue` pakub child-route'idele `provide()` kaudu konteksti (näiteks `isOrganizer` ja `tripCurrencySymbol`). |
| 89 | |
| 90 | **Avalikud ruudid:** `/`, `/login`, `/register` |
| 91 | **Autentimist nõudvad ruudid:** kõik ülejäänud (valvur `router/index.ts` suunab külalised `/login` peale) |
| 92 | |
| 93 | Põhilised ruudid: |
| 94 | - `/trips` — reiside loend |
| 95 | - `/trips/create` — uus reis |
| 96 | - `/trips/:tripId` — reisi detailvaade (parent) |
| 97 | - `expenses`, `expenses/create`, `expenses/:id/edit` |
| 98 | - `budget`, `budget/create`, `budget/:id/edit` |
| 99 | - `wishlist`, `wishlist/create`, `wishlist/:id/edit` |
| 100 | - `polls`, `polls/create`, `polls/:id` |
| 101 | - `members` — reisikaaslased |
| 102 | - `settlement` — arveldused |
| 103 | - `edit` — reisi muutmine |
| 104 | - `/invitations/:token` — kutse vastuvõtmine |
| 105 | |
| 106 | ### 2.4 Autentimine |
| 107 | |
| 108 | **Salvestus:** JWT ja refresh token hoitakse `localStorage`-s (`jwt`, `refreshToken`, `userName`). Pinia store `auth.ts` sünkroniseerib need automaatselt `watch`-i kaudu. |
| 109 | |
| 110 | **Voog:** |
| 111 | 1. Login/Register → `AccountService` saadab POST päringu → saab tagasi `{ jwt, refreshToken, firstName, lastName }` |
| 112 | 2. `httpClient`-i **request interceptor** lisab iga päringule päise `Authorization: Bearer <jwt>` |
| 113 | 3. **Response interceptor** püüab 401 vastused kinni: |
| 114 | - Kutsub `refreshTokenAsync()` → uuendab tokenid store'is → kordab algset päringut |
| 115 | - Kui refresh ebaõnnestub → logib kasutaja välja ja suunab `/login`-ile |
| 116 | 4. Logout → tühistab refresh tokeni backendis + puhastab store'i |
| 117 | |
| 118 | ### 2.5 Keskkonnamuutujad |
| 119 | |
| 120 | Fail `.env` (gitignore'is): |
| 121 | ```env |
| 122 | VITE_API_BASE_URL=http://localhost:90/api/v1/ |
| 123 | ``` |
| 124 | |
| 125 | Kasutatakse `httpClient.ts` ja `AccountService.ts` failides. Kui backend käib lokaalselt `dotnet run` kaudu, tuleb see vahetada `http://localhost:5086/api/v1/` vastu. |
| 126 | |
| 127 | ### 2.6 Kulude jagamise loogika |
| 128 | |
| 129 | `SplitMethodSelector.vue` toetab nelja meetodit — need vastavad täpselt backendi `ESplitMethod` enumile: |
| 130 | |
| 131 | | Meetod | Kirjeldus | |
| 132 | |---|---| |
| 133 | | **EqualAll** | Kogu summa jagatakse võrdselt kõigi osalejate vahel | |
| 134 | | **EqualSubset** | Kasutaja valib alamhulga osalejatest, jagatakse võrdselt | |
| 135 | | **ExactAmounts** | Iga osaleja kohta sisestatakse täpne summa (peab võrduma kogusummaga) | |
| 136 | | **Percentages** | Iga osaleja kohta protsent (peavad kokku andma 100%) | |
| 137 | |
| 138 | Komponent valideerib sisendit jooksvalt ja emiteerib `update:splits` + `update:valid`. |
| 139 | |
| 140 | ### 2.7 Skriptid |
| 141 | |
| 142 | ```sh |
| 143 | npm install # Installi sõltuvused |
| 144 | npm run dev # Arendusserver http://localhost:5173 |
| 145 | npm run build # Type-check + production build |
| 146 | npm run preview # Eelvaade ehitatud rakendusest |
| 147 | npm run test:unit # Vitest testid |
| 148 | npm run lint # Oxlint + ESLint |
| 149 | npm run format # Prettier |
| 150 | ``` |
| 151 | |
| 152 | > **Märkus:** `src/__tests__/App.spec.ts` on aegunud (otsib teksti "You did it!", mida App.vue ei sisalda). `npm run test:unit` kukub seetõttu praegu läbi. Reaalset funktsionaalsust see ei mõjuta. |
| 153 | |
| 154 | --- |
| 155 | |
| 156 | ## 3. Backend — `rasmju-csweb-phase3` |
| 157 | |
| 158 | ### 3.1 Tehnoloogiad |
| 159 | |
| 160 | - **.NET 10** (net10.0) |
| 161 | - **ASP.NET Core 10** Web API + Identity + MVC (Razor Views) |
| 162 | - **Entity Framework Core 10** — ORM |
| 163 | - **PostgreSQL 16** (Npgsql) — andmebaas, kolm schema-isoleeritud schema-t ühes andmebaasis |
| 164 | - **MediatR** — moodulitevaheline suhtlus (in-process queries + notifications) |
| 165 | - **JWT Bearer** autentimine + refresh token |
| 166 | - **Swashbuckle** — Swagger/OpenAPI UI |
| 167 | - **Asp.Versioning** — API versioonimine (`/api/v{version}/...`) |
| 168 | |
| 169 | Backend on **modulaarmonoliit** — üks deployitav rakendus, mille sees on kolm sisemiselt isoleeritud moodulit (**Users**, **Trips**, **Expenses**). Moodulid ei tee otseseid ristviiteid `Application`/`Infrastructure`/`Api` projektide tasemel — kõik moodulitevahelised väljakutsed käivad **MediatR-i kaudu**, igal moodulil on oma Postgres-i schema ja oma `DbContext`. |
| 170 | |
| 171 | ### 3.2 Lahuse struktuur |
| 172 | |
| 173 | `SplitApp.Modular/` lahus sisaldab kolme moodulit (kummalgi 4 projekti), kahte jagatud projekti ja ühte hosti: |
| 174 | |
| 175 | ``` |
| 176 | SplitApp.Modular/ |
| 177 | ├── SplitApp.sln |
| 178 | ├── Directory.Build.props |
| 179 | ├── src/ |
| 180 | │ ├── SplitApp.WebApp/ # Composition root + host |
| 181 | │ │ ├── Program.cs # AddXxxModule(...) wiring |
| 182 | │ │ ├── Application/ # Phase-2-st tõstetud BLL |
| 183 | │ │ │ ├── Services/ (+ Admin/, Identity/) # 12 admin + 9 klient + 1 identity teenust |
| 184 | │ │ │ ├── DTO/ # BllDto'd vaadetele |
| 185 | │ │ │ ├── Mappers/ # Domain ↔ BllDto factory mapperid |
| 186 | │ │ │ ├── Persistence/AppUnitOfWork.cs # Aggregib 3 mooduli DbContextid |
| 187 | │ │ │ └── Persistence/CrossModuleNavigationLoader.cs |
| 188 | │ │ ├── Areas/Admin/ # MVC admin haldusliides (13 kontrollerit) |
| 189 | │ │ ├── Areas/Identity/ # Razor Identity UI (Register jne) |
| 190 | │ │ ├── Controllers/, Views/ # Klient-MVC (parity phase 2-ga) |
| 191 | │ │ └── Resources/ # i18n resx (en, et) |
| 192 | │ ├── Shared/ |
| 193 | │ │ ├── SplitApp.Shared.Kernel/ # BaseEntity, IBaseRepo, IUoW, LangStr |
| 194 | │ │ └── SplitApp.Shared.Contracts/ # MediatR IRequest / INotification |
| 195 | │ └── Modules/ |
| 196 | │ ├── Users/ # 4 projekti, schema "users" |
| 197 | │ │ ├── ...Domain/ # AppUser, AppRole, AppRefreshToken |
| 198 | │ │ ├── ...Application/ # IIdentityService, JWT/refresh, MediatR handlerid |
| 199 | │ │ ├── ...Infrastructure/ # UsersDbContext, repod, migrations |
| 200 | │ │ └── ...Api/ # /api/v1/identity/... |
| 201 | │ ├── Trips/ # 4 projekti, schema "trips" |
| 202 | │ └── Expenses/ # 4 projekti, schema "expenses" |
| 203 | └── tests/ |
| 204 | ├── SplitApp.Modules.{Users,Trips,Expenses}.Tests/ # Per-module unit testid |
| 205 | └── SplitApp.WebApp.IntegrationTests/ # Architecture invariant + smoke (25 testi kokku) |
| 206 | ``` |
| 207 | |
| 208 | Iga moodul = mini-Clean-Architecture (`Domain` ← `Application` ← `Infrastructure`, `Api` REST jaoks). `Application`/`Infrastructure`/`Api` projektidel ei ole `<ProjectReference>`-i teiste moodulite samanimelistele projektidele — seda kontrollivad `tests/SplitApp.WebApp.IntegrationTests/Architecture/` arhitektuuritestid (kukkumine = ehitus kukub). |
| 209 | |
| 210 | ### 3.3 Domeenimudel |
| 211 | |
| 212 | Põhientiteedid ja nende seosed: |
| 213 | |
| 214 | | Entiteet | Olulised väljad | Seosed | |
| 215 | |---|---|---| |
| 216 | | **Trip** | Name, Description, Destination, StartDate, EndDate, Status, DefaultCurrencyId, CreatedById | 1→many: Participants, Expenses, BudgetCategories, WishlistItems, Polls, Invitations, SettlementPlans | |
| 217 | | **TripParticipant** | TripId, UserId, Role (Organizer/Participant), Nickname, IsActive | ↔ Trip, AppUser | |
| 218 | | **Expense** | TripId, PaidByUserId, Amount, Description, ExpenseDate, SplitMethod, BudgetCategoryId, CurrencyId | 1→many: ExpenseSplits | |
| 219 | | **ExpenseSplit** | ExpenseId, UserId, Amount, Percentage | ↔ Expense, AppUser | |
| 220 | | **BudgetCategory** | TripId, Name (LangStr), IconName, PlannedAmount | 1→many: Expenses | |
| 221 | | **TripInvitation** | TripId, Token (unikaalne), Status, ExpiresAt | ↔ Trip | |
| 222 | | **TripPoll** | TripId, Question, AllowMultipleVotes, IsAnonymous, ClosedAt | 1→many: Options → Votes | |
| 223 | | **TripWishlistItem** | TripId, Title, Category, Priority, EstimatedCost, IsCompleted | 1→many: Votes | |
| 224 | | **SettlementPlan** | TripId, TotalAmount, Status | 1→many: Payments | |
| 225 | | **SettlementPayment** | FromUserId, ToUserId, Amount, Status (Pending/MarkedPaid/Confirmed) | ↔ SettlementPlan | |
| 226 | | **Currency** | Code (3 tähte), Name (LangStr), Symbol | — | |
| 227 | |
| 228 | **Identity entiteedid (Users moodul, schema `users`):** |
| 229 | - `AppUser` (laiendab `IdentityUser<Guid>`) — lisab `FirstName`, `LastName` |
| 230 | - `AppRole` (laiendab `IdentityRole<Guid>`) — rollid: `user`, `admin` |
| 231 | - `AppRefreshToken` — refresh token + eelmine token + aegumised |
| 232 | |
| 233 | **Baasinfrastruktuur (`SplitApp.Shared.Kernel`):** |
| 234 | - `BaseEntity` — abstraktne: `Id` (Guid), `CreatedAt`, `UpdatedAt` |
| 235 | - `LangStr` — mitmekeelne string (`Dictionary<string, string>`), salvestatakse andmebaasis JSON-ina |
| 236 | |
| 237 | **Enumid:** |
| 238 | - `ETripStatus`: Active, **Finalizing**, Settled, Archived (Finalizing = arvelduskava lukus, oodatakse kinnitusi) |
| 239 | - `EParticipantRole`: Organizer, Participant |
| 240 | - `ESplitMethod`: **EqualAll, EqualSubset, ExactAmounts, Percentages** (vastab frontendi omale) |
| 241 | - `EInvitationStatus`: Pending, Accepted, Declined, Expired, Revoked |
| 242 | - `ESettlementStatus`: Pending, InProgress, Completed |
| 243 | - `EPaymentStatus`: Pending, MarkedPaid, Confirmed |
| 244 | |
| 245 | **Schemade jaotus:** |
| 246 | |
| 247 | | Schema | Moodul | Entiteedid | |
| 248 | |---|---|---| |
| 249 | | `users` | Users | AppUser, AppRole + ASP.NET Identity tabelid, AppRefreshToken | |
| 250 | | `trips` | Trips | Trip, TripParticipant, TripInvitation, TripPoll(+Option/Vote), TripWishlistItem(+Vote), BudgetCategory | |
| 251 | | `expenses` | Expenses | Currency, Expense, ExpenseSplit, SettlementPlan, SettlementPayment, SplitPreset(+Member) | |
| 252 | |
| 253 | **Cross-module entiteedi-viited** (näit `Trip.CreatedById`, `Expense.PaidByUserId`, `BudgetCategory.TripId`) on lihtsalt `Guid` väljad — **ei mingit EF foreign key piirangut üle schema-de**, sest EF-l ei tohi lasta kogemata schema piiri ületada. Navigeerimispropertid (näit `Trip.DefaultCurrency`, `Trip.CreatedBy`, `Expense.PaidByUser`) on `[NotMapped]` ja täidetakse käsitsi `WebApp/Application/Persistence/CrossModuleNavigationLoader`-i kaudu (eraldi batch-päringud teise mooduli `DbContext`-i vastu). |
| 254 | |
| 255 | Referentsiaalne terviklikkus tagatakse kahel viisil: |
| 256 | 1. **Eelnev validatsioon MediatR-päringutega** — näit enne kulu salvestamist saadab `ExpensesController` `IsTripParticipantQuery(tripId, userId)` Trips moodulile |
| 257 | 2. **Domain-eventide koristus kustutamisel** — `UserDeletedEvent` / `TripDeletedEvent` ja teised moodulid tellivad need ning kustutavad sõltuvad read |
| 258 | |
| 259 | ### 3.4 Andmebaas |
| 260 | |
| 261 | **Kolm DbContext-i, üks andmebaas:** kõik kolm moodulit ühenduvad samasse Postgres-i andmebaasi, kuid igaüks oma schema kaudu. Kogu konfiguratsioon on per-moodul mooduli enda `Infrastructure/Persistence/`-s. |
| 262 | |
| 263 | | DbContext | Schema | Pärib | Asukoht | |
| 264 | |---|---|---|---| |
| 265 | | `UsersDbContext` | `users` | `IdentityDbContext<AppUser, AppRole, Guid>` (rakendab ka `IDataProtectionKeyContext`) | `Modules/Users/...Infrastructure/Persistence/` | |
| 266 | | `TripsDbContext` | `trips` | `DbContext` | `Modules/Trips/...Infrastructure/Persistence/` | |
| 267 | | `ExpensesDbContext` | `expenses` | `DbContext` | `Modules/Expenses/...Infrastructure/Persistence/` | |
| 268 | |
| 269 | **Cross-schema SQL JOIN-id on keelatud** — kompositsioon toimub rakenduskihis MediatR-i või `CrossModuleNavigationLoader`-i kaudu. Et `Trips.Domain.Trip.DefaultCurrency` (Currency on Expenses moodulis) tüüpi ikkagi näha saaks, on `Trips.Domain` ja `Expenses.Domain` projektidel **kolm Domain-to-Domain `<ProjectReference>`-i** — aga kõik cross-module navigeerimispropertid on `[NotMapped]`, nii et EF ei lähe kunagi üle schema-piiri. |
| 270 | |
| 271 | **Olulised piirangud (OnModelCreating, igas DbContext-is):** |
| 272 | - Kõik seosed mooduli sees: `DeleteBehavior.Restrict` (kustutamisel ei kustutata kaskaadis — välja arvatud cleanup eventidel) |
| 273 | - Unikaalne indeks: `TripInvitation.Token` |
| 274 | - Liit-unikaalne indeks: `(TripParticipant.TripId, UserId)`, `(TripWishlistVote.WishlistItemId, UserId)`, `(TripPollVote.PollOptionId, UserId)` |
| 275 | - DateTime väljad: UTC konverter (alati salvestatakse UTC-s) — `UtcDateTimeConverter` igas mooduli `Persistence/` kaustas |
| 276 | - `LangStr` väljad (`Currency.Name`, `BudgetCategory.Name`): JSON-serialiseeritud |
| 277 | |
| 278 | **Migratsioonid:** 1 migratsioon mooduli kohta (`Init`), kokku 3: |
| 279 | - `Modules/Users/...Infrastructure/Persistence/Migrations/` |
| 280 | - `Modules/Trips/...Infrastructure/Persistence/Migrations/` |
| 281 | - `Modules/Expenses/...Infrastructure/Persistence/Migrations/` |
| 282 | |
| 283 | Migratsioonid rakendatakse host'i käivitamisel automaatselt — iga mooduli `UseXxxModule()` extension call (`Program.cs`) teeb `db.Database.Migrate()`. |
| 284 | |
| 285 | **Andmete lähtestamine** (seadistatakse `appsettings.json`-is): |
| 286 | ```json |
| 287 | "DataInitialization": { |
| 288 | "DropDatabase": false, // Kustuta andmebaas käivitamisel |
| 289 | "MigrateDatabase": true, // Rakenda migratsioonid |
| 290 | "SeedIdentity": true, // Loo kasutajad ja rollid (Users moodul) |
| 291 | "SeedData": true // Loo näidisandmed (host-tasandil, peale module init'e) |
| 292 | } |
| 293 | ``` |
| 294 | |
| 295 | **Seemneandmed:** |
| 296 | - **5 demokasutajat** (parool kõigil: `Kala.12345`) — seedib `UsersModuleExtensions.UseUsersModule()`: |
| 297 | - `user@taltech.ee`, `alice@taltech.ee`, `bob@taltech.ee`, `charlie@taltech.ee`, `diana@taltech.ee` |
| 298 | - admin luuakse ainult siis, kui `SEED_ADMIN_PASSWORD` on seatud |
| 299 | - **5 valuutat:** EUR, USD, GBP, SEK, NOK — seedib Expenses moodul |
| 300 | - **4 näidisreisi:** Barcelona Weekend (Active), London Business Trip (Settled), Summer Cabin Getaway (Active), NYC Adventure (Archived) — host-tasandi cross-module seed (`SplitApp.WebApp/Hosting/AppDataInit.cs`), kuna sisaldab andmeid kõigist kolmest moodulist |
| 301 | |
| 302 | ### 3.5 API lõpp-punktid |
| 303 | |
| 304 | Kõik kontrollerid on versioonitud: `/api/v{version:apiVersion}/` (vaikimisi v1.0). Kontrollerid elavad **iga mooduli oma `Api/` projektis** — host (WebApp) avastab need läbi `AddApplicationPart(...)` (`Program.cs`). |
| 305 | |
| 306 | **Identity / Account** (Users moodul) — `/api/v1/identity/account` |
| 307 | |
| 308 | | Verb | Lõpp-punkt | Otstarve | Auth | |
| 309 | |---|---|---|---| |
| 310 | | POST | `/register` | Kasutaja registreerimine | — | |
| 311 | | POST | `/login` | Login → JWT + refresh token | — | |
| 312 | | POST | `/refreshtokendata` | Tokenite uuendamine | — | |
| 313 | | POST | `/logout` | Refresh tokeni tühistamine | JWT | |
| 314 | |
| 315 | **Trips** (Trips moodul) — `/api/v1/trips` |
| 316 | |
| 317 | | Verb | Lõpp-punkt | Otstarve | |
| 318 | |---|---|---| |
| 319 | | GET | `/` | Kasutaja reiside loend | |
| 320 | | POST | `/` | Uus reis | |
| 321 | | GET | `/{id}` | Reisi detail | |
| 322 | | PUT | `/{id}` | Muuda reisi | |
| 323 | | DELETE | `/{id}` | Kustuta reis | |
| 324 | | GET | `/{tripId}/participants` | Osalejate loend | |
| 325 | | DELETE | `/{tripId}/participants/{userId}` | Eemalda osaleja | |
| 326 | |
| 327 | **Expenses** (Expenses moodul) — `/api/v1/expenses` |
| 328 | - GET `/trip/{tripId}`, POST `/`, GET `/{id}`, PUT `/{id}`, DELETE `/{id}` |
| 329 | |
| 330 | **BudgetCategories** (Trips moodul) — `/api/v1/budgetcategories` |
| 331 | - GET `/trip/{tripId}`, POST `/`, PUT `/{id}`, DELETE `/{id}` |
| 332 | |
| 333 | **Currencies** (Expenses moodul) — `/api/v1/currencies` |
| 334 | - GET `/` (avalik, ei vaja JWT-d) |
| 335 | |
| 336 | **Invitations** (Trips moodul) — `/api/v1/invitations` |
| 337 | - POST `/`, GET `/{token}`, POST `/{token}/accept`, POST `/{token}/decline`, POST `/{token}/revoke` |
| 338 | |
| 339 | **Polls** (Trips moodul) — `/api/v1/polls` |
| 340 | - GET `/trip/{tripId}`, POST `/`, GET `/{id}`, POST `/{id}/vote`, POST `/{id}/close`, DELETE `/{id}` |
| 341 | |
| 342 | **Wishlist** (Trips moodul) — `/api/v1/wishlist` |
| 343 | - GET `/trip/{tripId}`, POST `/`, PUT `/{id}`, DELETE `/{id}`, POST `/{id}/vote`, POST `/{id}/complete` |
| 344 | |
| 345 | **Settlements** (Expenses moodul) — `/api/v1/settlements` |
| 346 | - GET `/trip/{tripId}`, GET `/trip/{tripId}/summary`, GET `/trip/{tripId}/balances` |
| 347 | - POST `/trip/{tripId}/calculate` — arvutab ja loob uue arvelduskava |
| 348 | - POST `/payments/{paymentId}/mark-paid` — märgib makse tasutuks |
| 349 | - POST `/payments/{paymentId}/confirm` — kinnitab saamise |
| 350 | |
| 351 | **SplitPresets** (Expenses moodul) — `/api/v1/splitpresets` |
| 352 | - GET `/trip/{tripId}`, GET `/{id}`, POST `/`, PUT `/{id}`, DELETE `/{id}` |
| 353 | |
| 354 | ### 3.6 Autentimine ja turvalisus |
| 355 | |
| 356 | **JWT seadistus (appsettings.json):** |
| 357 | ```json |
| 358 | "JWT": { |
| 359 | "Issuer": "itcollege.taltech.ee", |
| 360 | "Audience": "itcollege.taltech.ee", |
| 361 | "ExpiresInSeconds": 1800 // 30 minutit |
| 362 | } |
| 363 | ``` |
| 364 | |
| 365 | **Voog:** |
| 366 | 1. `POST /login` → tagastab JWT (30 min) + `AppRefreshToken` (kehtib 7 päeva) |
| 367 | 2. Frontend kasutab JWT-d iga päringul |
| 368 | 3. Enne aegumist (või 401 korral) → `POST /refreshtokendata` → uus JWT + uus refresh token (vana märgitakse eelmiseks, kehtib veel 1 minut ühildumiseks) |
| 369 | 4. `POST /logout` → kustutab refresh tokeni andmebaasist |
| 370 | |
| 371 | **Autoriseerimine kontrollerites (IDOR-kaitse):** |
| 372 | - Enamik lõpp-punkte: `[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]` |
| 373 | - Kasutaja-id loetakse JWT `nameidentifier` claimist iga päringu alguses |
| 374 | - **Cross-module osaleja-kontroll käib MediatR-i kaudu** — näit `ExpensesController` saadab `IsTripParticipantQuery(tripId, userId)` Trips moodulile, sest Expenses moodul ei tohi `TripsDbContext`-i otse näha. Tagastab `Forbid()` kui false. |
| 375 | - Reisi-sisene loaja kontroll (organizer-only mutatsioon): `if (trip.CreatedById != userId.Value) return Forbid();` |
| 376 | - `List` endpointide nähtavus filtreeritud serveri pool — kasutaja ei näe kunagi reise/kulusid mille trip-osalemine puudub |
| 377 | |
| 378 | ### 3.7 CORS |
| 379 | |
| 380 | `Program.cs` seadistab poliitika `CorsAllowAll`: |
| 381 | ```csharp |
| 382 | .AllowAnyOrigin() |
| 383 | .AllowAnyMethod() |
| 384 | .AllowAnyHeader() |
| 385 | ``` |
| 386 | |
| 387 | See tähendab, et frontend võib olla mistahes pordil/domeenil — arenduseks mugav, tootmises tuleks piirata. |
| 388 | |
| 389 | ### 3.8 Konfiguratsioon (appsettings.json) |
| 390 | |
| 391 | ```json |
| 392 | "ConnectionStrings": { |
| 393 | "DefaultConnection": "Host=localhost;Port=5432;Database=splitapp;Username=postgres;Password=postgres" |
| 394 | }, |
| 395 | "SupportedCultures": ["en", "et"], |
| 396 | "DefaultCulture": "en" |
| 397 | ``` |
| 398 | |
| 399 | **Lokaliseerimine:** toetab inglise ja eesti keelt. Kultuuri saab muuta query parameetriga `?culture=et` või küpsise kaudu. |
| 400 | |
| 401 | ### 3.9 Käivitamine |
| 402 | |
| 403 | **Lokaalselt (dotnet):** |
| 404 | ```sh |
| 405 | cd SplitApp.Modular/src/SplitApp.WebApp |
| 406 | dotnet run |
| 407 | ``` |
| 408 | → `http://localhost:5086` (http) või `https://localhost:7040` (https) |
| 409 | |
| 410 | **Dockeri kaudu (soovitatud):** |
| 411 | ```sh |
| 412 | docker compose up --build |
| 413 | ``` |
| 414 | → backend: `http://localhost:90` |
| 415 | → Postgres: `localhost:5432` |
| 416 | |
| 417 | --- |
| 418 | |
| 419 | ## 4. Kuidas frontend ja backend koos töötavad |
| 420 | |
| 421 | ### 4.1 Pordid |
| 422 | |
| 423 | | Teenus | Port | Kus | |
| 424 | |---|---|---| |
| 425 | | Backend API | **90** | Docker (host) → 8080 (container) | |
| 426 | | Backend API (lokaalselt) | 5086 (http) / 7040 (https) | `dotnet run` | |
| 427 | | PostgreSQL | 5432 | Docker | |
| 428 | | Frontend (prod, Nginx) | **91** | Docker | |
| 429 | | Frontend (dev, Vite) | 5173 | `npm run dev` või Docker `dev` profiil | |
| 430 | |
| 431 | **Oluline:** frontend kuulab vaikimisi 8080-le *sisemist* konteineri porti, kuid Docker Compose avaldab selle hoopis **91**-le, et vältida konflikti lokaalse backendiga. Backend on host'il pordil **90**. |
| 432 | |
| 433 | ### 4.2 API URL |
| 434 | |
| 435 | Frontendi `.env`: |
| 436 | ```env |
| 437 | VITE_API_BASE_URL=http://localhost:90/api/v1/ |
| 438 | ``` |
| 439 | |
| 440 | Kui backend käib lokaalselt (`dotnet run`): |
| 441 | ```env |
| 442 | VITE_API_BASE_URL=http://localhost:5086/api/v1/ |
| 443 | ``` |
| 444 | |
| 445 | ### 4.3 Lõpp-punktide vastavustabel |
| 446 | |
| 447 | | Frontend teenus | Frontendi kõne | Backendi kontroller | |
| 448 | |---|---|---| |
| 449 | | AccountService | `identity/Account/Login` | `AccountController.Login` (Users moodul) | |
| 450 | | AccountService | `identity/Account/Register` | `AccountController.Register` (Users moodul) | |
| 451 | | AccountService | `identity/Account/RefreshTokenData` | `AccountController.RefreshTokenData` (Users moodul) | |
| 452 | | AccountService | `identity/Account/Logout` | `AccountController.Logout` (Users moodul) | |
| 453 | | TripService | `Trips/*` | `TripsController` | |
| 454 | | ExpenseService | `Expenses/*` | `ExpensesController` | |
| 455 | | BudgetCategoryService | `BudgetCategories/*` | `BudgetCategoriesController` | |
| 456 | | CurrencyService | `Currencies` | `CurrenciesController` | |
| 457 | | PollService | `Polls/*`, `Polls/:id/vote`, `Polls/:id/close` | `PollsController` | |
| 458 | | WishlistService | `Wishlist/*`, `:id/vote`, `:id/complete` | `WishlistController` | |
| 459 | | SettlementService | `Settlements/trip/:id/*`, `payments/:id/mark-paid` | `SettlementsController` | |
| 460 | | InvitationService | `Invitations/:token/*` | `InvitationsController` | |
| 461 | |
| 462 | ASP.NET Core ruutimine on **tõstutundetu**, seega erinevused nagu `Trips` vs `trips` ei tekita probleemi. |
| 463 | |
| 464 | ### 4.4 Andmetüüpide vastavus |
| 465 | |
| 466 | Frontendi TypeScript liidesed (`src/types/`) on loodud backendi DTO-de põhjal ning struktuurid vastavad 1:1. Näiteks: |
| 467 | |
| 468 | **Frontend `IExpense`** ↔ **Backend `ExpenseDto`** |
| 469 | ``` |
| 470 | id, tripId, paidByUserId, budgetCategoryId, currencyId, |
| 471 | amount, description, expenseDate, splitMethod, splits[] |
| 472 | ``` |
| 473 | |
| 474 | **Frontend `IJwtResponse`** ↔ **Backend `JWTResponse`** |
| 475 | ``` |
| 476 | jwt, refreshToken, firstName, lastName |
| 477 | ``` |
| 478 | |
| 479 | Kulude jagamise enum `ESplitMethod` on mõlemas pooles identne. |
| 480 | |
| 481 | ### 4.5 Autentimise koostöö |
| 482 | |
| 483 | 1. Frontend saadab `POST /api/v1/identity/account/login` |
| 484 | 2. Backend valideerib ja tagastab `{ jwt, refreshToken, firstName, lastName }` |
| 485 | 3. Frontend salvestab need `localStorage`-sse ja Pinia store'i |
| 486 | 4. Iga järgmine päring → `httpClient` lisab `Authorization: Bearer <jwt>` |
| 487 | 5. Kui backend tagastab 401 → frontend proovib automaatselt refresh'i |
| 488 | 6. Backend valideerib refresh tokeni, loob uue JWT + uue refresh tokeni ja tagastab |
| 489 | 7. Frontend uuendab store'i ja kordab algset päringut |
| 490 | |
| 491 | --- |
| 492 | |
| 493 | ## 5. Käivitamise juhend |
| 494 | |
| 495 | ### 5.1 Täielik Docker-käivitus (soovitatud) |
| 496 | |
| 497 | **1. Käivita backend + andmebaas:** |
| 498 | ```sh |
| 499 | cd C:\Users\rasmu\Documents\csharpweb\rasmju-csweb-phase3 |
| 500 | docker compose up --build |
| 501 | ``` |
| 502 | See käivitab: |
| 503 | - PostgreSQL pordil 5432 |
| 504 | - Backend API pordil 90 |
| 505 | - Rakendab migratsioonid ja laeb seemneandmed |
| 506 | |
| 507 | **2. Kontrolli, et backend vastab:** |
| 508 | Avaga brauseris `http://localhost:90/swagger` — peaks kuvama Swagger UI. |
| 509 | |
| 510 | **3. Käivita frontend:** |
| 511 | |
| 512 | Arendusrežiimis (soovitatud koodi muutmiseks): |
| 513 | ```sh |
| 514 | cd C:\Users\rasmu\Documents\javascript\rasmju-js-a7 |
| 515 | npm install |
| 516 | npm run dev |
| 517 | ``` |
| 518 | → `http://localhost:5173` |
| 519 | |
| 520 | Või Dockeri kaudu production build: |
| 521 | ```sh |
| 522 | docker compose up --build |
| 523 | ``` |
| 524 | → `http://localhost:91` |
| 525 | |
| 526 | ### 5.2 Kiire test |
| 527 | |
| 528 | 1. Ava `http://localhost:5173` (või `:91`) |
| 529 | 2. Logi sisse seemneandmete kasutajaga: |
| 530 | - Email: `alice@taltech.ee` |
| 531 | - Parool: `Kala.12345` |
| 532 | 3. Peaksid nägema reiside loendit (Barcelona Weekend, Summer Cabin Getaway, ...) |
| 533 | 4. Ava üks reis → vaata kulusid, arveldusi, küsitlusi |
| 534 | |
| 535 | ### 5.3 Lokaalne arendus (ilma Dockerita) |
| 536 | |
| 537 | **Backend:** |
| 538 | ```sh |
| 539 | cd rasmju-csweb-phase3/SplitApp.Modular/src/SplitApp.WebApp |
| 540 | dotnet run |
| 541 | ``` |
| 542 | → `http://localhost:5086` |
| 543 | |
| 544 | **Frontend** (muuda `.env`): |
| 545 | ```env |
| 546 | VITE_API_BASE_URL=http://localhost:5086/api/v1/ |
| 547 | ``` |
| 548 | ```sh |
| 549 | cd rasmju-js-a7 |
| 550 | npm run dev |
| 551 | ``` |
| 552 | |
| 553 | --- |
| 554 | |
| 555 | ## 6. CI/CD pipeline |
| 556 | |
| 557 | Fail `.gitlab-ci.yml` defineerib **ühe etapi: deploy**. Testid jooksevad lokaalselt enne push'i (vt jaotis 7) — pipeline'i sisse on test-etapp **teadlikult lisamata**, sest varasem versioon kukus runneril ja blokeeris deploy'd. |
| 558 | |
| 559 | ### 6.1 Deploy-etapp |
| 560 | |
| 561 | Käivitub **ainult `main` harusse merge'imisel**. Kasutab VPS-il olevat self-hosted runner'it (`shared` tag): |
| 562 | |
| 563 | ```sh |
| 564 | docker compose -p rasmju-js-a7 up --build --remove-orphans --detach |
| 565 | ``` |
| 566 | |
| 567 | - Ehitab uue multi-stage Docker image'i (Node build → Nginx) |
| 568 | - Käivitab uue konteineri pordil **91** |
| 569 | - Projektinimi `rasmju-js-a7` hoiab konteinerid backendi omadest eraldi |
| 570 | - `--remove-orphans` koristab vanad konteinerid |
| 571 | |
| 572 | ### 6.2 Lokaalne kontrollnimekiri enne push'i |
| 573 | |
| 574 | Pipeline ei tee neid samme automaatselt — käivita käsitsi: |
| 575 | |
| 576 | ```sh |
| 577 | npm run lint # Oxlint + ESLint |
| 578 | npm run type-check # vue-tsc range tüübikontroll |
| 579 | npm run test:unit -- --run # Vitest unit + integration |
| 580 | npm run test:e2e # Playwright (vajab elavat backendi) |
| 581 | ``` |
| 582 | |
| 583 | Kui kõik on roheline, alles siis `git push`. |
| 584 | |
| 585 | ### 6.3 Eraldi hosting ja CORS |
| 586 | |
| 587 | Frontend on hostitud **eraldi URL-il ja pordil** backendist: |
| 588 | - Frontend: `https://travel.rasmusj.com` (Nginx, port 91) |
| 589 | - Backend: `https://travel.rasmusj.com` (ASP.NET, port 90) |
| 590 | |
| 591 | Kuna tegemist on eri origin'itega, on backend seadistatud lubama päringuid kõigilt domeenidelt (`AllowAnyOrigin` CORS-poliitika `Program.cs`-is). Frontend ei vaja CORS-i seadistamist — see on puhtalt backendi vastutus. |
| 592 | |
| 593 | --- |
| 594 | |
| 595 | ## 7. Testimine |
| 596 | |
| 597 | Projektil on kolm testimise kihti — **39 testi 7 failis**, kõik rohelised phase 3 modulaarmonoliit-backendi vastu. **Unit ja integration testid ei vaja backendi** (MSW mockib võrku); E2E testid käivad päris brauseris päris backendi vastu. |
| 598 | |
| 599 | ### 7.1 Unit testid (Vitest + jsdom) |
| 600 | |
| 601 | Kaust: `src/__tests__/unit/`. Iga fail testib ühte kitsalt piiritletud koodiosa puhaste sisendite-väljunditega. |
| 602 | |
| 603 | | Testifail | Mida testib | Testide arv | Mis vea see püüaks | |
| 604 | |---|---|---|---| |
| 605 | | `formatCurrency.spec.ts` | märk-, sümbol-, kümnendkoha-, tuhandete-eraldaja-vormindus | **7** | Kuvatakse igal lehel — regression rikuks visuaalselt kogu äpi | |
| 606 | | `parseJwt.spec.ts` | JWT base64url dekodeerimine, ASP.NET `nameidentifier` claim + `sub` fallback | **9** | Vale user-id parsing seoks kulud vaikselt vale kasutajaga | |
| 607 | | `auth-store.spec.ts` | Pinia auth store: localStorage hüdratsioon, `isAuthenticated` reaktiivsus, `logout()`, watcher-based sync | **6** | Sünkroonimise lõhe → kasutaja näeks reload-i järel vana identiteeti | |
| 608 | | `SplitMethodSelector.spec.ts` | kõik 4 jagamismeetodit, jooksev validatsioon, edit-režiim `existingSplits`-iga | **9** | Kõige keerulisem äriloogika frondis — invalid splittidega salvestatud kulu | |
| 609 | |
| 610 | **Kokku:** 31 unit-testi, jooks ~0.4s. |
| 611 | |
| 612 | ### 7.2 Integration test (Vitest + MSW) |
| 613 | |
| 614 | Kaust: `src/__tests__/integration/`. Erinevalt unit-testist mockib MSW võrku, mitte axios funktsioone — niisiis axios käitub täpselt nagu prodis. |
| 615 | |
| 616 | | Testifail | Mida testib | Testide arv | |
| 617 | |---|---|---| |
| 618 | | `token-refresh.spec.ts` | `httpClient`-i 401 → refresh → retry pipeline, kogu otsast otsani: (1) request interceptor lisab Bearer; (2) edukas refresh + originaalpäringu kordamine; (3) refresh kukub → logout → redirect `/login`-ile; (4) refresh token puudub → otse logout | **4** | |
| 619 | |
| 620 | See on **app-i kõige väärtuslikum test** — katab turvalisuse-tundlikuima glue-i (axios interceptors + Pinia store + router). Käsitsi seda flow-d katsetada nõuaks JWT aegumise ootamist või manuaalset katki tegemist. |
| 621 | |
| 622 | ### 7.3 End-to-end testid (Playwright + Chromium) |
| 623 | |
| 624 | Kaust: `e2e/`. Päris brauser, päris backend. `webServer` config käivitab automaatselt `npm run dev`-i, niisiis vajalik on ainult backendi kättesaadavus. |
| 625 | |
| 626 | | Testifail | Mida testib | Testide arv | |
| 627 | |---|---|---| |
| 628 | | `auth.spec.ts` | (1) login seemnekasutajaga `alice@taltech.ee` → logout; (2) vale parool jätab `/login`-ile; (3) kaitstud `/trips` redirectib anonüümse `/login`-ile | **3** | |
| 629 | | `trip-crud.spec.ts` | **Positive happy flow** — login → loo unikaalse nimega reis → näe seda nimekirjas → ava Edit → muuda nime → kontrolli et muudatus on nähtav | **1** | |
| 630 | |
| 631 | **Märkus `trip-crud` kohta:** Delete on teadlikult välja jäetud, sest backend `DeleteBehavior.Restrict` blokeerib reisi kustutamise, kui sel on ükskõik milline TripParticipant (alati on vähemalt loojast Organizer). See on backend-i probleem, mitte frondi oma — niisiis test katsetab Update-i selle asemel, et pipeline jääks roheline. |
| 632 | |
| 633 | **Playwright seadistus** (`playwright.config.ts`): |
| 634 | - Brauser: ainult Chromium |
| 635 | - Käivitab automaatselt Vite arendusserveri |
| 636 | - `.env`-ist loetav `VITE_API_BASE_URL` määrab, mille vastu test jookseb (praegu: prod backend) |
| 637 | - Üks worker (jadatestid jagatud seemneandmete tõttu) |
| 638 | - Trace + screenshot ebaõnnestumisel |
| 639 | |
| 640 | ### 7.4 Testiskriptid |
| 641 | |
| 642 | ```sh |
| 643 | npm run test:unit # Vitest watch-režiim |
| 644 | npm run test:unit -- --run # Ühekordne jooks |
| 645 | npm run test:e2e # Playwright headless (vajab backendi kättesaadavust) |
| 646 | npm run test:e2e:ui # Playwright interaktiivne UI |
| 647 | ``` |
| 648 | |
| 649 | ### 7.5 Kuidas E2E käib phase 3 vastu |
| 650 | |
| 651 | Frontendi `.env` osutab vaikimisi prod backendile: |
| 652 | |
| 653 | ```env |
| 654 | VITE_API_BASE_URL=https://travel.rasmusj.com/api/v1/ |
| 655 | ``` |
| 656 | |
| 657 | Niisiis `npm run test:e2e` lokaalselt: |
| 658 | 1. Käivitab Vite dev-serveri pordil 5173 |
| 659 | 2. Vite serveerib Vue äppi, mis HTTP-päringutes osutab prod backendile |
| 660 | 3. Playwright juhib Chromiumi → login `alice@taltech.ee` → CRUD operatsioonid → tulemused tulevad päris prod-DB-st |
| 661 | |
| 662 | ⚠️ `trip-crud` test loob päris reisi prod-andmebaasi ja **ei kustuta seda** (vt 7.3 märkust). Kui taht olla puhtam, siis vaheta `.env.local`-iga lokaalse Dockeri vastu. |
| 663 | |
| 664 | ### 7.6 Kokkuvõte |
| 665 | |
| 666 | | Kiht | Raamistik | Failide arv | Testide arv | Vajab backendi? | Aeg | |
| 667 | |---|---|---|---|---|---| |
| 668 | | Unit | Vitest + jsdom | 4 | 31 | Ei | ~0.4s | |
| 669 | | Integration | Vitest + MSW | 1 | 4 | Ei | ~0.5s | |
| 670 | | E2E | Playwright + Chromium | 2 | 4 | Jah | ~11s | |
| 671 | | **Kokku** | | **7** | **39** | | ~12s | |
| 672 | |
| 673 | --- |
| 674 | |
| 675 | ## 8. Seemneandmete kasutajad |
| 676 | |
| 677 | Demokasutajatel on parool **`Kala.12345`**. See on koodis teadlikult: tegu on |
| 678 | demoga ja andmed on välja mõeldud. |
| 679 | |
| 680 | Admin on eraldi. Ta luuakse ainult siis, kui serveripoolel on seatud |
| 681 | `SEED_ADMIN_PASSWORD`, ja vaikeväärtust ei ole. Ilma selle muutujata ei ole |
| 682 | admin-kontot üldse olemas. |
| 683 | |
| 684 | | Email | Roll | |
| 685 | |---|---| |
| 686 | | `user@taltech.ee` | user | |
| 687 | | `alice@taltech.ee` | user | |
| 688 | | `bob@taltech.ee` | user | |
| 689 | | `charlie@taltech.ee` | user | |
| 690 | | `diana@taltech.ee` | user | |
| 691 | |
| 692 | --- |
| 693 | |
| 694 | ## 9. Kokkuvõte — kas kõik töötab? |
| 695 | |
| 696 | | Kontroll | Staatus | |
| 697 | |---|---| |
| 698 | | Frontendi `VITE_API_BASE_URL` viitab backendi Dockeri pordile (90) | ✅ | |
| 699 | | API versioon `v1` vastab backendi vaikeversioonile | ✅ | |
| 700 | | Kõik 9 frontendi teenust leiavad backendist vastava kontrolleri | ✅ | |
| 701 | | JWT vastuse struktuur on mõlemas pooles sama | ✅ | |
| 702 | | Refresh-tokeni voog (request → refresh → retry) | ✅ | |
| 703 | | CORS lubab frontendi origini (AllowAnyOrigin) | ✅ | |
| 704 | | `ESplitMethod` enum sama mõlemas pooles | ✅ | |
| 705 | | Pordikonfliktid puuduvad (90, 5432, 91, 5173) | ✅ | |
| 706 | | Seemneandmed (6 kasutajat, 4 reisi) laaditakse käivitamisel | ✅ | |
| 707 | |
| 708 | ### Ülesande nõuete vastavus |
| 709 | |
| 710 | | Nõue | Staatus | Kus | |
| 711 | |---|---|---| |
| 712 | | Eraldiseisev klientrakendus valitud tehnoloogiaga | ✅ | Vue 3 + TypeScript | |
| 713 | | Kasutab oma backendi REST API-t | ✅ | `VITE_API_BASE_URL`, 9 teenust | |
| 714 | | JWT + refresh token autentimine | ✅ | `httpClient.ts`, `AccountService.ts`, `auth.ts` | |
| 715 | | Login / logout | ✅ | `LoginView.vue`, `RegisterView.vue`, `App.vue` | |
| 716 | | CRUD vähemalt 3 entiteedil | ✅ (5) | Trips, Expenses, BudgetCategories, Wishlist, Polls | |
| 717 | | CI/CD deploy — klient eraldi URL-il | ✅ | `.gitlab-ci.yml`, eraldi Docker konteiner pordil 91 | |
| 718 | | CORS käsitlus | ✅ | Backend `CorsAllowAll` poliitika; frontend eraldi origin'il | |
| 719 | | Boonus: unit + integration + e2e testid | ✅ | Vitest + MSW + Playwright — 39 testi (kõik rohelised phase 3 vastu) | |
| 720 | |