Projekti ülevaade — SplitApp (Reisikulude jagaja)
See dokument kirjeldab tervet süsteemi: frontendi (Vue 3) ja backendi (ASP.NET Core 10) ning seda, kuidas need omavahel suhtlevad.
1. Mis on SplitApp?
SplitApp on reisiplaneerimise ja ühiste kulude jagamise rakendus. Kasutajad saavad:
- luua reise ja kutsuda kaaslasi
- sisestada ühiseid kulusid ja jagada neid nelja erineva meetodi järgi
- hallata eelarvekategooriaid ja jälgida kulutusi
- pidada soovinimekirja tegevustest/kohtadest koos hääletusega
- teha grupiotsuseid küsitluste kaudu
- lõpuks arvestada, kes kellele võlgu on (arveldused)
Süsteem koosneb kahest eraldiseisvast projektist, mis suhtlevad REST API kaudu:
| Projekt | Kaust | Tehnoloogia |
|---|---|---|
| Frontend | rasmju-js-a7 |
Vue 3 + TypeScript + Vite |
| Backend | rasmju-csweb-phase3 |
ASP.NET Core 10 + PostgreSQL |
2. Frontend — rasmju-js-a7
2.1 Tehnoloogiad
- Vue 3.5 (Composition API) + TypeScript 6
- Vite 8 — build tool ja arendusserver
- Vue Router 5 — lehekülgede ruutimine
- Pinia 3 — olekuhaldus
- Axios 1.14 — HTTP klient
- Bootstrap 5.3 — stiilide raamistik
- Vitest 4 — unit testid
- ESLint + Oxlint + Prettier — koodi kvaliteet
2.2 Projekti struktuur
src/
├── components/ # Jagatud komponendid
│ ├── SplitMethodSelector.vue # Kulu jagamise UI (4 meetodit)
│ └── ToastContainer.vue # Teavituste kuvamine
├── composables/
│ └── useToast.ts # Globaalne teavituste süsteem
├── directives/
│ └── vAnimate.ts # Scroll-animatsiooni direktiiv
├── router/
│ └── index.ts # Kõik ruudid + autentimise valvur
├── services/ # API kliendid
│ ├── httpClient.ts # Axiose seadistus + interceptorid
│ ├── AccountService.ts # Login, register, refresh, logout
│ ├── TripService.ts # Reisid
│ ├── ExpenseService.ts # Kulud
│ ├── BudgetCategoryService.ts # Eelarvekategooriad
│ ├── CurrencyService.ts # Valuutad
│ ├── PollService.ts # Küsitlused
│ ├── WishlistService.ts # Soovinimekiri
│ ├── SettlementService.ts # Arveldused
│ └── InvitationService.ts # Kutsed
├── stores/
│ └── auth.ts # JWT + refreshToken + userName
├── types/ # TypeScript liidesed (DTO vastavalt API-le)
├── utils/
│ ├── formatCurrency.ts # Valuuta vormindus
│ └── parseJwt.ts # JWT dekodeerimine
├── views/ # Leheküljed
│ ├── HomeView.vue
│ ├── LoginView.vue
│ ├── RegisterView.vue
│ ├── trips/ # IndexView, DetailView, CreateView, EditView
│ ├── expenses/ # Index, Create, Edit
│ ├── budget-categories/ # Index, Create, Edit
│ ├── wishlist/ # Index, Create, Edit
│ ├── polls/ # Index, Create, Detail
│ ├── invitations/ # AcceptView
│ ├── members/ # MembersView
│ └── settlements/ # SettlementView
├── App.vue # Juurkomponent (navbar + router-view)
└── main.ts # Käivituspunkt
2.3 Ruutimine
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).
Avalikud ruudid: /, /login, /register
Autentimist nõudvad ruudid: kõik ülejäänud (valvur router/index.ts suunab külalised /login peale)
Põhilised ruudid:
/trips— reiside loend/trips/create— uus reis/trips/:tripId— reisi detailvaade (parent)expenses,expenses/create,expenses/:id/editbudget,budget/create,budget/:id/editwishlist,wishlist/create,wishlist/:id/editpolls,polls/create,polls/:idmembers— reisikaaslasedsettlement— arveldusededit— reisi muutmine
/invitations/:token— kutse vastuvõtmine
2.4 Autentimine
Salvestus: JWT ja refresh token hoitakse localStorage-s (jwt, refreshToken, userName). Pinia store auth.ts sünkroniseerib need automaatselt watch-i kaudu.
Voog:
- Login/Register →
AccountServicesaadab POST päringu → saab tagasi{ jwt, refreshToken, firstName, lastName } httpClient-i request interceptor lisab iga päringule päiseAuthorization: Bearer <jwt>- Response interceptor püüab 401 vastused kinni:
- Kutsub
refreshTokenAsync()→ uuendab tokenid store'is → kordab algset päringut - Kui refresh ebaõnnestub → logib kasutaja välja ja suunab
/login-ile
- Kutsub
- Logout → tühistab refresh tokeni backendis + puhastab store'i
2.5 Keskkonnamuutujad
Fail .env (gitignore'is):
VITE_API_BASE_URL=http://localhost:90/api/v1/
Kasutatakse httpClient.ts ja AccountService.ts failides. Kui backend käib lokaalselt dotnet run kaudu, tuleb see vahetada http://localhost:5086/api/v1/ vastu.
2.6 Kulude jagamise loogika
SplitMethodSelector.vue toetab nelja meetodit — need vastavad täpselt backendi ESplitMethod enumile:
| Meetod | Kirjeldus |
|---|---|
| EqualAll | Kogu summa jagatakse võrdselt kõigi osalejate vahel |
| EqualSubset | Kasutaja valib alamhulga osalejatest, jagatakse võrdselt |
| ExactAmounts | Iga osaleja kohta sisestatakse täpne summa (peab võrduma kogusummaga) |
| Percentages | Iga osaleja kohta protsent (peavad kokku andma 100%) |
Komponent valideerib sisendit jooksvalt ja emiteerib update:splits + update:valid.
2.7 Skriptid
npm install # Installi sõltuvused
npm run dev # Arendusserver http://localhost:5173
npm run build # Type-check + production build
npm run preview # Eelvaade ehitatud rakendusest
npm run test:unit # Vitest testid
npm run lint # Oxlint + ESLint
npm run format # Prettier
Märkus:
src/__tests__/App.spec.tson aegunud (otsib teksti "You did it!", mida App.vue ei sisalda).npm run test:unitkukub seetõttu praegu läbi. Reaalset funktsionaalsust see ei mõjuta.
3. Backend — rasmju-csweb-phase3
3.1 Tehnoloogiad
- .NET 10 (net10.0)
- ASP.NET Core 10 Web API + Identity + MVC (Razor Views)
- Entity Framework Core 10 — ORM
- PostgreSQL 16 (Npgsql) — andmebaas, kolm schema-isoleeritud schema-t ühes andmebaasis
- MediatR — moodulitevaheline suhtlus (in-process queries + notifications)
- JWT Bearer autentimine + refresh token
- Swashbuckle — Swagger/OpenAPI UI
- Asp.Versioning — API versioonimine (
/api/v{version}/...)
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.
3.2 Lahuse struktuur
SplitApp.Modular/ lahus sisaldab kolme moodulit (kummalgi 4 projekti), kahte jagatud projekti ja ühte hosti:
SplitApp.Modular/
├── SplitApp.sln
├── Directory.Build.props
├── src/
│ ├── SplitApp.WebApp/ # Composition root + host
│ │ ├── Program.cs # AddXxxModule(...) wiring
│ │ ├── Application/ # Phase-2-st tõstetud BLL
│ │ │ ├── Services/ (+ Admin/, Identity/) # 12 admin + 9 klient + 1 identity teenust
│ │ │ ├── DTO/ # BllDto'd vaadetele
│ │ │ ├── Mappers/ # Domain ↔ BllDto factory mapperid
│ │ │ ├── Persistence/AppUnitOfWork.cs # Aggregib 3 mooduli DbContextid
│ │ │ └── Persistence/CrossModuleNavigationLoader.cs
│ │ ├── Areas/Admin/ # MVC admin haldusliides (13 kontrollerit)
│ │ ├── Areas/Identity/ # Razor Identity UI (Register jne)
│ │ ├── Controllers/, Views/ # Klient-MVC (parity phase 2-ga)
│ │ └── Resources/ # i18n resx (en, et)
│ ├── Shared/
│ │ ├── SplitApp.Shared.Kernel/ # BaseEntity, IBaseRepo, IUoW, LangStr
│ │ └── SplitApp.Shared.Contracts/ # MediatR IRequest / INotification
│ └── Modules/
│ ├── Users/ # 4 projekti, schema "users"
│ │ ├── ...Domain/ # AppUser, AppRole, AppRefreshToken
│ │ ├── ...Application/ # IIdentityService, JWT/refresh, MediatR handlerid
│ │ ├── ...Infrastructure/ # UsersDbContext, repod, migrations
│ │ └── ...Api/ # /api/v1/identity/...
│ ├── Trips/ # 4 projekti, schema "trips"
│ └── Expenses/ # 4 projekti, schema "expenses"
└── tests/
├── SplitApp.Modules.{Users,Trips,Expenses}.Tests/ # Per-module unit testid
└── SplitApp.WebApp.IntegrationTests/ # Architecture invariant + smoke (25 testi kokku)
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).
3.3 Domeenimudel
Põhientiteedid ja nende seosed:
| Entiteet | Olulised väljad | Seosed |
|---|---|---|
| Trip | Name, Description, Destination, StartDate, EndDate, Status, DefaultCurrencyId, CreatedById | 1→many: Participants, Expenses, BudgetCategories, WishlistItems, Polls, Invitations, SettlementPlans |
| TripParticipant | TripId, UserId, Role (Organizer/Participant), Nickname, IsActive | ↔ Trip, AppUser |
| Expense | TripId, PaidByUserId, Amount, Description, ExpenseDate, SplitMethod, BudgetCategoryId, CurrencyId | 1→many: ExpenseSplits |
| ExpenseSplit | ExpenseId, UserId, Amount, Percentage | ↔ Expense, AppUser |
| BudgetCategory | TripId, Name (LangStr), IconName, PlannedAmount | 1→many: Expenses |
| TripInvitation | TripId, Token (unikaalne), Status, ExpiresAt | ↔ Trip |
| TripPoll | TripId, Question, AllowMultipleVotes, IsAnonymous, ClosedAt | 1→many: Options → Votes |
| TripWishlistItem | TripId, Title, Category, Priority, EstimatedCost, IsCompleted | 1→many: Votes |
| SettlementPlan | TripId, TotalAmount, Status | 1→many: Payments |
| SettlementPayment | FromUserId, ToUserId, Amount, Status (Pending/MarkedPaid/Confirmed) | ↔ SettlementPlan |
| Currency | Code (3 tähte), Name (LangStr), Symbol | — |
Identity entiteedid (Users moodul, schema users):
AppUser(laiendabIdentityUser<Guid>) — lisabFirstName,LastNameAppRole(laiendabIdentityRole<Guid>) — rollid:user,adminAppRefreshToken— refresh token + eelmine token + aegumised
Baasinfrastruktuur (SplitApp.Shared.Kernel):
BaseEntity— abstraktne:Id(Guid),CreatedAt,UpdatedAtLangStr— mitmekeelne string (Dictionary<string, string>), salvestatakse andmebaasis JSON-ina
Enumid:
ETripStatus: Active, Finalizing, Settled, Archived (Finalizing = arvelduskava lukus, oodatakse kinnitusi)EParticipantRole: Organizer, ParticipantESplitMethod: EqualAll, EqualSubset, ExactAmounts, Percentages (vastab frontendi omale)EInvitationStatus: Pending, Accepted, Declined, Expired, RevokedESettlementStatus: Pending, InProgress, CompletedEPaymentStatus: Pending, MarkedPaid, Confirmed
Schemade jaotus:
| Schema | Moodul | Entiteedid |
|---|---|---|
users |
Users | AppUser, AppRole + ASP.NET Identity tabelid, AppRefreshToken |
trips |
Trips | Trip, TripParticipant, TripInvitation, TripPoll(+Option/Vote), TripWishlistItem(+Vote), BudgetCategory |
expenses |
Expenses | Currency, Expense, ExpenseSplit, SettlementPlan, SettlementPayment, SplitPreset(+Member) |
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).
Referentsiaalne terviklikkus tagatakse kahel viisil:
- Eelnev validatsioon MediatR-päringutega — näit enne kulu salvestamist saadab
ExpensesControllerIsTripParticipantQuery(tripId, userId)Trips moodulile - Domain-eventide koristus kustutamisel —
UserDeletedEvent/TripDeletedEventja teised moodulid tellivad need ning kustutavad sõltuvad read
3.4 Andmebaas
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.
| DbContext | Schema | Pärib | Asukoht |
|---|---|---|---|
UsersDbContext |
users |
IdentityDbContext<AppUser, AppRole, Guid> (rakendab ka IDataProtectionKeyContext) |
Modules/Users/...Infrastructure/Persistence/ |
TripsDbContext |
trips |
DbContext |
Modules/Trips/...Infrastructure/Persistence/ |
ExpensesDbContext |
expenses |
DbContext |
Modules/Expenses/...Infrastructure/Persistence/ |
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.
Olulised piirangud (OnModelCreating, igas DbContext-is):
- Kõik seosed mooduli sees:
DeleteBehavior.Restrict(kustutamisel ei kustutata kaskaadis — välja arvatud cleanup eventidel) - Unikaalne indeks:
TripInvitation.Token - Liit-unikaalne indeks:
(TripParticipant.TripId, UserId),(TripWishlistVote.WishlistItemId, UserId),(TripPollVote.PollOptionId, UserId) - DateTime väljad: UTC konverter (alati salvestatakse UTC-s) —
UtcDateTimeConverterigas mooduliPersistence/kaustas LangStrväljad (Currency.Name,BudgetCategory.Name): JSON-serialiseeritud
Migratsioonid: 1 migratsioon mooduli kohta (Init), kokku 3:
Modules/Users/...Infrastructure/Persistence/Migrations/Modules/Trips/...Infrastructure/Persistence/Migrations/Modules/Expenses/...Infrastructure/Persistence/Migrations/
Migratsioonid rakendatakse host'i käivitamisel automaatselt — iga mooduli UseXxxModule() extension call (Program.cs) teeb db.Database.Migrate().
Andmete lähtestamine (seadistatakse appsettings.json-is):
"DataInitialization": {
"DropDatabase": false, // Kustuta andmebaas käivitamisel
"MigrateDatabase": true, // Rakenda migratsioonid
"SeedIdentity": true, // Loo kasutajad ja rollid (Users moodul)
"SeedData": true // Loo näidisandmed (host-tasandil, peale module init'e)
}
Seemneandmed:
- 5 demokasutajat (parool kõigil:
Kala.12345) — seedibUsersModuleExtensions.UseUsersModule():user@taltech.ee,alice@taltech.ee,bob@taltech.ee,charlie@taltech.ee,diana@taltech.ee- admin luuakse ainult siis, kui
SEED_ADMIN_PASSWORDon seatud
- 5 valuutat: EUR, USD, GBP, SEK, NOK — seedib Expenses moodul
- 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
3.5 API lõpp-punktid
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).
Identity / Account (Users moodul) — /api/v1/identity/account
| Verb | Lõpp-punkt | Otstarve | Auth |
|---|---|---|---|
| POST | /register |
Kasutaja registreerimine | — |
| POST | /login |
Login → JWT + refresh token | — |
| POST | /refreshtokendata |
Tokenite uuendamine | — |
| POST | /logout |
Refresh tokeni tühistamine | JWT |
Trips (Trips moodul) — /api/v1/trips
| Verb | Lõpp-punkt | Otstarve |
|---|---|---|
| GET | / |
Kasutaja reiside loend |
| POST | / |
Uus reis |
| GET | /{id} |
Reisi detail |
| PUT | /{id} |
Muuda reisi |
| DELETE | /{id} |
Kustuta reis |
| GET | /{tripId}/participants |
Osalejate loend |
| DELETE | /{tripId}/participants/{userId} |
Eemalda osaleja |
Expenses (Expenses moodul) — /api/v1/expenses
- GET
/trip/{tripId}, POST/, GET/{id}, PUT/{id}, DELETE/{id}
BudgetCategories (Trips moodul) — /api/v1/budgetcategories
- GET
/trip/{tripId}, POST/, PUT/{id}, DELETE/{id}
Currencies (Expenses moodul) — /api/v1/currencies
- GET
/(avalik, ei vaja JWT-d)
Invitations (Trips moodul) — /api/v1/invitations
- POST
/, GET/{token}, POST/{token}/accept, POST/{token}/decline, POST/{token}/revoke
Polls (Trips moodul) — /api/v1/polls
- GET
/trip/{tripId}, POST/, GET/{id}, POST/{id}/vote, POST/{id}/close, DELETE/{id}
Wishlist (Trips moodul) — /api/v1/wishlist
- GET
/trip/{tripId}, POST/, PUT/{id}, DELETE/{id}, POST/{id}/vote, POST/{id}/complete
Settlements (Expenses moodul) — /api/v1/settlements
- GET
/trip/{tripId}, GET/trip/{tripId}/summary, GET/trip/{tripId}/balances - POST
/trip/{tripId}/calculate— arvutab ja loob uue arvelduskava - POST
/payments/{paymentId}/mark-paid— märgib makse tasutuks - POST
/payments/{paymentId}/confirm— kinnitab saamise
SplitPresets (Expenses moodul) — /api/v1/splitpresets
- GET
/trip/{tripId}, GET/{id}, POST/, PUT/{id}, DELETE/{id}
3.6 Autentimine ja turvalisus
JWT seadistus (appsettings.json):
"JWT": {
"Issuer": "itcollege.taltech.ee",
"Audience": "itcollege.taltech.ee",
"ExpiresInSeconds": 1800 // 30 minutit
}
Voog:
POST /login→ tagastab JWT (30 min) +AppRefreshToken(kehtib 7 päeva)- Frontend kasutab JWT-d iga päringul
- Enne aegumist (või 401 korral) →
POST /refreshtokendata→ uus JWT + uus refresh token (vana märgitakse eelmiseks, kehtib veel 1 minut ühildumiseks) POST /logout→ kustutab refresh tokeni andmebaasist
Autoriseerimine kontrollerites (IDOR-kaitse):
- Enamik lõpp-punkte:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - Kasutaja-id loetakse JWT
nameidentifierclaimist iga päringu alguses - Cross-module osaleja-kontroll käib MediatR-i kaudu — näit
ExpensesControllersaadabIsTripParticipantQuery(tripId, userId)Trips moodulile, sest Expenses moodul ei tohiTripsDbContext-i otse näha. TagastabForbid()kui false. - Reisi-sisene loaja kontroll (organizer-only mutatsioon):
if (trip.CreatedById != userId.Value) return Forbid(); Listendpointide nähtavus filtreeritud serveri pool — kasutaja ei näe kunagi reise/kulusid mille trip-osalemine puudub
3.7 CORS
Program.cs seadistab poliitika CorsAllowAll:
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
See tähendab, et frontend võib olla mistahes pordil/domeenil — arenduseks mugav, tootmises tuleks piirata.
3.8 Konfiguratsioon (appsettings.json)
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=splitapp;Username=postgres;Password=postgres"
},
"SupportedCultures": ["en", "et"],
"DefaultCulture": "en"
Lokaliseerimine: toetab inglise ja eesti keelt. Kultuuri saab muuta query parameetriga ?culture=et või küpsise kaudu.
3.9 Käivitamine
Lokaalselt (dotnet):
cd SplitApp.Modular/src/SplitApp.WebApp
dotnet run
→ http://localhost:5086 (http) või https://localhost:7040 (https)
Dockeri kaudu (soovitatud):
docker compose up --build
→ backend: http://localhost:90
→ Postgres: localhost:5432
4. Kuidas frontend ja backend koos töötavad
4.1 Pordid
| Teenus | Port | Kus |
|---|---|---|
| Backend API | 90 | Docker (host) → 8080 (container) |
| Backend API (lokaalselt) | 5086 (http) / 7040 (https) | dotnet run |
| PostgreSQL | 5432 | Docker |
| Frontend (prod, Nginx) | 91 | Docker |
| Frontend (dev, Vite) | 5173 | npm run dev või Docker dev profiil |
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.
4.2 API URL
Frontendi .env:
VITE_API_BASE_URL=http://localhost:90/api/v1/
Kui backend käib lokaalselt (dotnet run):
VITE_API_BASE_URL=http://localhost:5086/api/v1/
4.3 Lõpp-punktide vastavustabel
| Frontend teenus | Frontendi kõne | Backendi kontroller |
|---|---|---|
| AccountService | identity/Account/Login |
AccountController.Login (Users moodul) |
| AccountService | identity/Account/Register |
AccountController.Register (Users moodul) |
| AccountService | identity/Account/RefreshTokenData |
AccountController.RefreshTokenData (Users moodul) |
| AccountService | identity/Account/Logout |
AccountController.Logout (Users moodul) |
| TripService | Trips/* |
TripsController |
| ExpenseService | Expenses/* |
ExpensesController |
| BudgetCategoryService | BudgetCategories/* |
BudgetCategoriesController |
| CurrencyService | Currencies |
CurrenciesController |
| PollService | Polls/*, Polls/:id/vote, Polls/:id/close |
PollsController |
| WishlistService | Wishlist/*, :id/vote, :id/complete |
WishlistController |
| SettlementService | Settlements/trip/:id/*, payments/:id/mark-paid |
SettlementsController |
| InvitationService | Invitations/:token/* |
InvitationsController |
ASP.NET Core ruutimine on tõstutundetu, seega erinevused nagu Trips vs trips ei tekita probleemi.
4.4 Andmetüüpide vastavus
Frontendi TypeScript liidesed (src/types/) on loodud backendi DTO-de põhjal ning struktuurid vastavad 1:1. Näiteks:
Frontend IExpense ↔ Backend ExpenseDto
id, tripId, paidByUserId, budgetCategoryId, currencyId,
amount, description, expenseDate, splitMethod, splits[]
Frontend IJwtResponse ↔ Backend JWTResponse
jwt, refreshToken, firstName, lastName
Kulude jagamise enum ESplitMethod on mõlemas pooles identne.
4.5 Autentimise koostöö
- Frontend saadab
POST /api/v1/identity/account/login - Backend valideerib ja tagastab
{ jwt, refreshToken, firstName, lastName } - Frontend salvestab need
localStorage-sse ja Pinia store'i - Iga järgmine päring →
httpClientlisabAuthorization: Bearer <jwt> - Kui backend tagastab 401 → frontend proovib automaatselt refresh'i
- Backend valideerib refresh tokeni, loob uue JWT + uue refresh tokeni ja tagastab
- Frontend uuendab store'i ja kordab algset päringut
5. Käivitamise juhend
5.1 Täielik Docker-käivitus (soovitatud)
1. Käivita backend + andmebaas:
cd C:\Users\rasmu\Documents\csharpweb\rasmju-csweb-phase3
docker compose up --build
See käivitab:
- PostgreSQL pordil 5432
- Backend API pordil 90
- Rakendab migratsioonid ja laeb seemneandmed
2. Kontrolli, et backend vastab:
Avaga brauseris http://localhost:90/swagger — peaks kuvama Swagger UI.
3. Käivita frontend:
Arendusrežiimis (soovitatud koodi muutmiseks):
cd C:\Users\rasmu\Documents\javascript\rasmju-js-a7
npm install
npm run dev
→ http://localhost:5173
Või Dockeri kaudu production build:
docker compose up --build
→ http://localhost:91
5.2 Kiire test
- Ava
http://localhost:5173(või:91) - Logi sisse seemneandmete kasutajaga:
- Email:
alice@taltech.ee - Parool:
Kala.12345
- Email:
- Peaksid nägema reiside loendit (Barcelona Weekend, Summer Cabin Getaway, ...)
- Ava üks reis → vaata kulusid, arveldusi, küsitlusi
5.3 Lokaalne arendus (ilma Dockerita)
Backend:
cd rasmju-csweb-phase3/SplitApp.Modular/src/SplitApp.WebApp
dotnet run
→ http://localhost:5086
Frontend (muuda .env):
VITE_API_BASE_URL=http://localhost:5086/api/v1/
cd rasmju-js-a7
npm run dev
6. CI/CD pipeline
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.
6.1 Deploy-etapp
Käivitub ainult main harusse merge'imisel. Kasutab VPS-il olevat self-hosted runner'it (shared tag):
docker compose -p rasmju-js-a7 up --build --remove-orphans --detach
- Ehitab uue multi-stage Docker image'i (Node build → Nginx)
- Käivitab uue konteineri pordil 91
- Projektinimi
rasmju-js-a7hoiab konteinerid backendi omadest eraldi --remove-orphanskoristab vanad konteinerid
6.2 Lokaalne kontrollnimekiri enne push'i
Pipeline ei tee neid samme automaatselt — käivita käsitsi:
npm run lint # Oxlint + ESLint
npm run type-check # vue-tsc range tüübikontroll
npm run test:unit -- --run # Vitest unit + integration
npm run test:e2e # Playwright (vajab elavat backendi)
Kui kõik on roheline, alles siis git push.
6.3 Eraldi hosting ja CORS
Frontend on hostitud eraldi URL-il ja pordil backendist:
- Frontend:
https://travel.rasmusj.com(Nginx, port 91) - Backend:
https://travel.rasmusj.com(ASP.NET, port 90)
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.
7. Testimine
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.
7.1 Unit testid (Vitest + jsdom)
Kaust: src/__tests__/unit/. Iga fail testib ühte kitsalt piiritletud koodiosa puhaste sisendite-väljunditega.
| Testifail | Mida testib | Testide arv | Mis vea see püüaks |
|---|---|---|---|
formatCurrency.spec.ts |
märk-, sümbol-, kümnendkoha-, tuhandete-eraldaja-vormindus | 7 | Kuvatakse igal lehel — regression rikuks visuaalselt kogu äpi |
parseJwt.spec.ts |
JWT base64url dekodeerimine, ASP.NET nameidentifier claim + sub fallback |
9 | Vale user-id parsing seoks kulud vaikselt vale kasutajaga |
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 |
SplitMethodSelector.spec.ts |
kõik 4 jagamismeetodit, jooksev validatsioon, edit-režiim existingSplits-iga |
9 | Kõige keerulisem äriloogika frondis — invalid splittidega salvestatud kulu |
Kokku: 31 unit-testi, jooks ~0.4s.
7.2 Integration test (Vitest + MSW)
Kaust: src/__tests__/integration/. Erinevalt unit-testist mockib MSW võrku, mitte axios funktsioone — niisiis axios käitub täpselt nagu prodis.
| Testifail | Mida testib | Testide arv |
|---|---|---|
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 |
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.
7.3 End-to-end testid (Playwright + Chromium)
Kaust: e2e/. Päris brauser, päris backend. webServer config käivitab automaatselt npm run dev-i, niisiis vajalik on ainult backendi kättesaadavus.
| Testifail | Mida testib | Testide arv |
|---|---|---|
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 |
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 |
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.
Playwright seadistus (playwright.config.ts):
- Brauser: ainult Chromium
- Käivitab automaatselt Vite arendusserveri
.env-ist loetavVITE_API_BASE_URLmäärab, mille vastu test jookseb (praegu: prod backend)- Üks worker (jadatestid jagatud seemneandmete tõttu)
- Trace + screenshot ebaõnnestumisel
7.4 Testiskriptid
npm run test:unit # Vitest watch-režiim
npm run test:unit -- --run # Ühekordne jooks
npm run test:e2e # Playwright headless (vajab backendi kättesaadavust)
npm run test:e2e:ui # Playwright interaktiivne UI
7.5 Kuidas E2E käib phase 3 vastu
Frontendi .env osutab vaikimisi prod backendile:
VITE_API_BASE_URL=https://travel.rasmusj.com/api/v1/
Niisiis npm run test:e2e lokaalselt:
- Käivitab Vite dev-serveri pordil 5173
- Vite serveerib Vue äppi, mis HTTP-päringutes osutab prod backendile
- Playwright juhib Chromiumi → login
alice@taltech.ee→ CRUD operatsioonid → tulemused tulevad päris prod-DB-st
⚠️ 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.
7.6 Kokkuvõte
| Kiht | Raamistik | Failide arv | Testide arv | Vajab backendi? | Aeg |
|---|---|---|---|---|---|
| Unit | Vitest + jsdom | 4 | 31 | Ei | ~0.4s |
| Integration | Vitest + MSW | 1 | 4 | Ei | ~0.5s |
| E2E | Playwright + Chromium | 2 | 4 | Jah | ~11s |
| Kokku | 7 | 39 | ~12s |
8. Seemneandmete kasutajad
Demokasutajatel on parool Kala.12345. See on koodis teadlikult: tegu on
demoga ja andmed on välja mõeldud.
Admin on eraldi. Ta luuakse ainult siis, kui serveripoolel on seatud
SEED_ADMIN_PASSWORD, ja vaikeväärtust ei ole. Ilma selle muutujata ei ole
admin-kontot üldse olemas.
| Roll | |
|---|---|
user@taltech.ee |
user |
alice@taltech.ee |
user |
bob@taltech.ee |
user |
charlie@taltech.ee |
user |
diana@taltech.ee |
user |
9. Kokkuvõte — kas kõik töötab?
| Kontroll | Staatus |
|---|---|
Frontendi VITE_API_BASE_URL viitab backendi Dockeri pordile (90) |
✅ |
API versioon v1 vastab backendi vaikeversioonile |
✅ |
| Kõik 9 frontendi teenust leiavad backendist vastava kontrolleri | ✅ |
| JWT vastuse struktuur on mõlemas pooles sama | ✅ |
| Refresh-tokeni voog (request → refresh → retry) | ✅ |
| CORS lubab frontendi origini (AllowAnyOrigin) | ✅ |
ESplitMethod enum sama mõlemas pooles |
✅ |
| Pordikonfliktid puuduvad (90, 5432, 91, 5173) | ✅ |
| Seemneandmed (6 kasutajat, 4 reisi) laaditakse käivitamisel | ✅ |
Ülesande nõuete vastavus
| Nõue | Staatus | Kus |
|---|---|---|
| Eraldiseisev klientrakendus valitud tehnoloogiaga | ✅ | Vue 3 + TypeScript |
| Kasutab oma backendi REST API-t | ✅ | VITE_API_BASE_URL, 9 teenust |
| JWT + refresh token autentimine | ✅ | httpClient.ts, AccountService.ts, auth.ts |
| Login / logout | ✅ | LoginView.vue, RegisterView.vue, App.vue |
| CRUD vähemalt 3 entiteedil | ✅ (5) | Trips, Expenses, BudgetCategories, Wishlist, Polls |
| CI/CD deploy — klient eraldi URL-il | ✅ | .gitlab-ci.yml, eraldi Docker konteiner pordil 91 |
| CORS käsitlus | ✅ | Backend CorsAllowAll poliitika; frontend eraldi origin'il |
| Boonus: unit + integration + e2e testid | ✅ | Vitest + MSW + Playwright — 39 testi (kõik rohelised phase 3 vastu) |