From 9fc5c1d6b0e42b3a512b84a30eb1eec894f0e2be Mon Sep 17 00:00:00 2001 From: Annika Holmqvist Date: Fri, 24 Apr 2026 20:29:44 +0200 Subject: [PATCH 1/2] docs: update README with architecture, domain model, API details, tech stack, and setup instructions --- README.md | 438 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 313 insertions(+), 125 deletions(-) diff --git a/README.md b/README.md index 871488bb..a106ac50 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,86 @@ -# Vet1177 – Veterinärjournalsystem (Backend) +# Vet1177 — Digital veterinärvård -Ett webbaserat journalhanteringssystem för veterinärkliniker. Husdjursägare kan skapa ärenden, veterinärer kan hantera journaler och bifoga filer, och administratörer har full tillgång till systemet. +Ett webbaserat journalhanteringssystem för veterinärkliniker, inspirerat av 1177.se. Djurägare registrerar ärenden för sina djur och kommunicerar med kliniken. Veterinärer tar ärenden, dokumenterar journaler och bifoga filer. Administratörer hanterar kliniker och användare. + +Projektet är fullstack: Spring Boot-backend med PostgreSQL + MinIO, och React-frontend. --- ## Innehåll +- [Arkitektur](#arkitektur) - [Tech-stack](#tech-stack) - [Kom igång](#kom-igång) - [Miljövariabler](#miljövariabler) - [Projektstruktur](#projektstruktur) -- [API-endpoints](#api-endpoints) +- [Domänmodell](#domänmodell) +- [API](#api) - [Roller och behörigheter](#roller-och-behörigheter) +- [Säkerhet](#säkerhet) - [Fillagring (MinIO/S3)](#fillagring-minios3) +- [Felhantering](#felhantering) - [Testning](#testning) - [CI/CD](#cicd) +- [Pågående och planerade arbeten](#pågående-och-planerade-arbeten) + +--- + +## Arkitektur + +``` +┌─────────────────────┐ JWT via Authorization header ┌─────────────────────┐ +│ │ ─────────────────────────────────────▶ │ │ +│ React SPA │ │ Spring Boot │ +│ (Vite + Tailwind) │ ◀───────────────────────────────────── │ REST API │ +│ │ JSON responses │ │ +└─────────────────────┘ └──────────┬──────────┘ + │ + ┌────────────────────┼────────────────────┐ + ▼ ▼ ▼ + ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ + │ PostgreSQL │ │ MinIO / S3 │ │ JWT-signed │ + │ (schema.sql)│ │ (attachments)│ │ tokens │ + └─────────────┘ └──────────────┘ └──────────────┘ +``` + +- Backend är en stateless REST-tjänst. Inga server-sessioner — identiteten bärs i JWT. +- Frontend är en separat SPA som bara pratar med backend via REST. +- Fillagring sker utanför relationsdatabasen: DB håller metadata, objekt-binärer ligger i MinIO (S3-kompatibel). +- Auktorisering är tvåstegs: grovmaskigt rollfilter på URL-nivå i Spring Security, finkornig ägarskap/kliniktillhörighet i dedikerade policy-klasser. --- ## Tech-stack -| Kategori | Teknologi | -|-----------------|------------------------------------| -| Språk | Java 25 | -| Ramverk | Spring Boot 4.0.4 | -| Databas | PostgreSQL 15 | -| ORM | Spring Data JPA / Hibernate | -| Säkerhet | Spring Security 6 | -| Fillagring | MinIO (S3-kompatibel) | -| Templating | Thymeleaf | -| Loggning | SLF4J | -| Byggsystem | Maven | -| Containerisering| Docker / Docker Compose | +### Backend + +| Kategori | Teknologi | +|---|---| +| Språk | Java 24 | +| Ramverk | Spring Boot 4.0.4 (MVC, Data JPA, Validation) | +| Säkerhet | Spring Security 7 + `spring-boot-starter-oauth2-resource-server` (JWT-validering) | +| Databas | PostgreSQL 15 | +| ORM | Hibernate (via Spring Data JPA) | +| Migration | `schema.sql` via `spring.sql.init` (Flyway planerad — se [Pågående arbete](#pågående-och-planerade-arbeten)) | +| Fillagring | MinIO (S3-kompatibel) via AWS SDK for Java v2 | +| Serialisering | Jackson Databind | +| Loggning | SLF4J | +| Test | JUnit 5, Mockito, AssertJ, Spring Security Test, `@WebMvcTest` | +| Coverage | JaCoCo | +| Byggsystem | Maven (med wrapper `mvnw`) | +| Containerisering | Docker Compose (Postgres + MinIO) | + +### Frontend + +| Kategori | Teknologi | +|---|---| +| Ramverk | React 19 | +| Build | Vite 8 | +| Styling | Tailwind CSS 3 | +| HTTP | Axios (med request/response-interceptors för JWT och 401/403-hantering) | +| JWT parsing | `jwt-decode` | +| Ikoner | Lucide React | +| Linting | ESLint 9 | --- @@ -39,9 +88,10 @@ Ett webbaserat journalhanteringssystem för veterinärkliniker. Husdjursägare k ### Förutsättningar -- Java 25 -- Maven +- Java 24 +- Maven (wrapper finns i repot) - Docker & Docker Compose +- Node.js 18+ och npm (för frontend) ### Installation @@ -62,12 +112,19 @@ Fyll i värdena i `.env` (se [Miljövariabler](#miljövariabler)). docker compose up -d ``` -**4. Starta applikationen** +**4. Starta backend** ```bash ./mvnw spring-boot:run ``` +Backenden startar på `http://localhost:8080`. -Applikationen startar på `http://localhost:8080`. +**5. Starta frontend (i ny terminal)** +```bash +cd frontend +npm install +npm run dev +``` +Frontenden startar på `http://localhost:5173`. > MinIO-konsolen nås på `http://localhost:9001` (använd dina `MINIO_ROOT_USER`/`MINIO_ROOT_PASSWORD`). @@ -75,166 +132,297 @@ Applikationen startar på `http://localhost:8080`. ## Miljövariabler -| Variabel | Beskrivning | Exempel | -|--------------------|----------------------------------|--------------------------------------------| -| `DB_URL` | PostgreSQL JDBC URL | `jdbc:postgresql://localhost:5432/vet1177` | -| `DB_USERNAME` | Databasanvändare | `postgres` | -| `DB_PASSWORD` | Databaslösenord | – | -| `DB_NAME` | Databasnamn | `vet1177` | -| `S3_ENDPOINT` | MinIO/S3 URL | `http://localhost:9000` | -| `S3_ACCESS_KEY` | S3 access key | – | -| `S3_SECRET_KEY` | S3 secret key | – | -| `S3_BUCKET` | Bucket-namn för bilagor | `vet1177-attachments` | -| `S3_REGION` | S3-region | `eu-north-1` | -| `MINIO_ROOT_USER` | MinIO admin-användare | – | -| `MINIO_ROOT_PASSWORD` | MinIO admin-lösenord | – | +| Variabel | Beskrivning | Exempel | +|---|---|---| +| `DB_URL` | PostgreSQL JDBC URL | `jdbc:postgresql://localhost:5432/vet1177` | +| `DB_USERNAME` | Databasanvändare | `postgres` | +| `DB_PASSWORD` | Databaslösenord | – | +| `DB_NAME` | Databasnamn | `vet1177` | +| `S3_ENDPOINT` | MinIO/S3-URL | `http://localhost:9000` | +| `S3_ACCESS_KEY` | S3 access key | – | +| `S3_SECRET_KEY` | S3 secret key | – | +| `S3_BUCKET` | Bucket-namn för bilagor | `vet1177-attachments` | +| `S3_REGION` | S3-region | `eu-north-1` | +| `MINIO_ROOT_USER` | MinIO admin-användare | – | +| `MINIO_ROOT_PASSWORD` | MinIO admin-lösenord | – | +| `JWT_SECRET` | Hemlig nyckel för JWT-signering (min. 32 tecken) | – | + +`jwt.expiration-ms` konfigureras i `application.properties` (default 24 h). Frontenden läser `VITE_API_BASE_URL` — defaultar till `http://localhost:8080/api`. --- ## Projektstruktur ``` -src/main/java/org/example/vet1177/ -├── config/ # Spring-konfiguration (MinIO/S3) -├── controller/ # REST-controllers -├── dto/ -│ ├── request/ # Inkommande request-objekt (med validering) -│ └── response/ # Utgående response-objekt -├── entities/ # JPA-entiteter -├── exception/ # Anpassade undantag och global felhanterare -├── policy/ # Auktoriseringslogik per roll -├── repository/ # Spring Data JPA-repositories -├── security/ # Spring Security-konfiguration + auth -│ └── auth/dto/ # Auth-relaterade DTOs -└── services/ # Affärslogik +. +├── src/main/java/org/example/vet1177/ +│ ├── config/ # AWS/MinIO-konfiguration +│ ├── controller/ # REST-controllers (Medical, Attachment, Comment, Pet, Clinic, Vet, User, ActivityLog) +│ ├── dto/ +│ │ ├── request/ # Inkommande DTO:er med Bean Validation +│ │ └── response/ # Utgående DTO:er +│ ├── entities/ # JPA-entiteter + enums (Role, RecordStatus, ActivityType, CommentType) +│ ├── exception/ # Custom exceptions + GlobalExceptionHandler +│ ├── policy/ # Auktoriseringsregler per domänobjekt (6 policies) +│ ├── repository/ # Spring Data JPA-repositories +│ ├── security/ # JwtService, JwtAuthenticationFilter, CustomUserDetailsService, +│ │ # SecurityConfig, DevSecurityConfig, AuthService, AuthController +│ └── services/ # Affärslogik +├── src/main/resources/ +│ ├── schema.sql # Tabelldefinitioner (idempotent med IF NOT EXISTS) +│ ├── data.sql # Seed-data för dev +│ ├── application.properties +│ └── application-dev.properties +├── src/test/java/... # 500+ tester: policy, service, controller, entity, integration +├── frontend/ +│ ├── src/ +│ │ ├── pages/ # CaseDetail, OwnerDashboard, VetDashboard, AdminDashboard, Login, Register, PetDetail, CreateCase +│ │ ├── components/ # Layout, PetForm, PetList, admin/* +│ │ ├── services/api.jsx # Axios-instans med interceptors +│ │ └── utils/statusHelper.js # Central svensk-mappning för status + ActivityType +│ └── package.json +├── docker-compose.yml # Postgres 15 + MinIO +├── .github/workflows/ci.yml # GitHub Actions +├── API.md # Detaljerad API-dokumentation +└── pom.xml ``` +--- + +## Domänmodell + ### Entiteter -| Entitet | Beskrivning | -|-----------------|----------------------------------------------------------| -| `User` | Användare med roll: `OWNER`, `VET`, `ADMIN` | -| `Vet` | Utökar User med licensnummer och specialisering | -| `Clinic` | Veterinärklinik | -| `Pet` | Husdjur knutet till en ägare och klinik | -| `MedicalRecord` | Ärende/journal – kärnan i systemet | -| `Comment` | Kommentar på ett ärende | -| `Attachment` | Bifogad fil lagrad i MinIO/S3 | -| `ActivityLog` | Revisionslogg över händelser på ett ärende | +| Entitet | Beskrivning | +|---|---| +| `User` | Användare med roll (`OWNER`, `VET`, `ADMIN`), ev. kopplad till en klinik | +| `Vet` | Veterinärprofil — kompletterar `User` med licens-ID och specialisering | +| `Clinic` | Klinik med adress, telefon, e-post | +| `Pet` | Djur kopplat till en ägare | +| `MedicalRecord` | Ärende/journal — kärnenhet. Har status, ägare, klinik, ev. tilldelad VET | +| `Comment` | Meddelande på ett ärende, typad som `OWNER_MESSAGE` eller `VET_CLINICAL_NOTE` | +| `Attachment` | Metadata för en bifogad fil — binären ligger i MinIO | +| `ActivityLog` | Revisionsspår av händelser på ärenden (separat från `medical_record`-tabellen) | ### Ärendestatus -`OPEN` → `IN_PROGRESS` → `AWAITING_INFO` → `CLOSED` +Status är en enum (`RecordStatus`): + +- `OPEN` — nytt ärende, inte tilldelat +- `IN_PROGRESS` — handläggning pågår (sätts automatiskt vid `assignVet`) +- `AWAITING_INFO` — väntar på komplettering från ägaren +- `CLOSED` — avslutat (slutstatus, kräver slutnotering) + +VET kan hoppa mellan `OPEN`, `IN_PROGRESS` och `AWAITING_INFO`. Stängning sker via en dedikerad endpoint som kräver en slutnotering. + +### Aktivitetstyper + +`ActivityType`-enumet används i revisionsspåret: `CASE_CREATED`, `STATUS_CHANGED`, `COMMENT_ADDED`, `ASSIGNED`, `UNASSIGNED`, `UPDATED`. --- -## API-endpoints +## API + +Bas-URL: `/api`. Alla endpoints kräver JWT i `Authorization: Bearer ` utom `/api/auth/**` och `GET /api/clinics/**`. + +Fullständig dokumentation finns i [`API.md`](./API.md). + +### Autentisering — `/api/auth` + +| Metod | Sökväg | Beskrivning | +|---|---|---| +| POST | `/login` | Logga in. Returnerar JWT + userinfo | +| POST | `/register` | Registrera ny OWNER. Returnerar JWT + userinfo | + +### Journaler — `/api/medical-records` + +| Metod | Sökväg | Beskrivning | +|---|---|---| +| POST | `/` | Skapa nytt ärende | +| GET | `/{id}` | Hämta ärende | +| GET | `/my-records` | Inloggad ägares ärenden | +| GET | `/my-assigned` | Inloggad veterinärs tilldelade ärenden | +| GET | `/owner/{ownerId}` | Ärenden för en ägare | +| GET | `/pet/{petId}` | Ärenden för ett djur | +| GET | `/clinic/{clinicId}` | Ärenden på en klinik | +| GET | `/clinic/{clinicId}/status/{status}` | Filtrera klinik + status | +| PUT | `/{id}` | Uppdatera titel/beskrivning (OWNER på eget, VET/ADMIN) | +| PUT | `/{id}/assign-vet` | Tilldela veterinär | +| PUT | `/{id}/unassign-vet` | Tilldelad VET släpper ärendet | +| PUT | `/{id}/status` | Ändra status (VET/ADMIN) | +| PUT | `/{id}/close` | Stäng ärende (VET/ADMIN) | + +### Övriga prefix + +| Prefix | Beskrivning | +|---|---| +| `/api/pets` | CRUD för djur | +| `/api/attachments` | Ladda upp, lista, hämta (med presigned URL), radera bilagor | +| `/api/comments` | Kommentera ärenden | +| `/api/activity-logs` | Revisionsspår per ärende / globalt (ADMIN) | +| `/api/clinics` | CRUD för kliniker (ADMIN) | +| `/api/users` | User management (ADMIN) | +| `/api/vets` | Veterinärprofiler | + +--- + +## Roller och behörigheter + +Auktoriseringen sker i två lager: + +**Lager 1 — URL-nivå** (`SecurityConfig`): grovmaskigt rollfilter via `requestMatchers(...).hasAnyRole(...)`. T.ex. `DELETE /api/attachments/**` kräver `VET` eller `ADMIN`. -Bas-URL: `/api` +**Lager 2 — Policy-klasser** (ett per domänobjekt): finkornig ägarskap / kliniktillhörighet / statuschecks. Kallas från service/controller innan mutationer. -### Journaler – `/api/medical-records` +| Policy-klass | Ansvar | +|---|---| +| `MedicalRecordPolicy` | canCreate / canView / canUpdate / canUpdateStatus / canClose / canAssignVet / canUnassignVet / canViewClinic | +| `AttachmentPolicy` | canUpload (MIME + size + ägarskap + CLOSED-spärr) / canDownload / canDelete | +| `CommentPolicy` | canCreate / canView / canUpdate / canDelete / isVisibleTo | +| `PetPolicy` | canUpdate / canDelete | +| `ActivityLogPolicy` | canView — endast ADMIN eller inblandade parter | +| `AdminPolicy` | Gate för ADMIN-only operationer | -| Metod | Sökväg | Beskrivning | -|--------|-------------------------------------|------------------------------------| -| POST | `/` | Skapa nytt ärende | -| GET | `/{id}` | Hämta ärende via ID | -| GET | `/my-records` | Inloggad ägares ärenden | -| GET | `/owner/{ownerId}` | Ärenden för en ägare | -| GET | `/pet/{petId}` | Ärenden för ett husdjur | -| GET | `/clinic/{clinicId}` | Ärenden på en klinik | -| GET | `/clinic/{clinicId}/status/{status}`| Filtrera på klinik och status | -| PUT | `/{id}` | Uppdatera ärende | -| PUT | `/{id}/assign-vet` | Tilldela veterinär | -| PUT | `/{id}/status` | Uppdatera status | -| PUT | `/{id}/close` | Stäng ärende | +### Rollmatris (sammanfattning) -### Övriga endpoints +| Handling | OWNER | VET | ADMIN | +|---|---|---|---| +| Skapa ärende för eget djur | ✅ | ✅ (egen klinik) | ✅ | +| Skapa ärende för annans djur | ❌ 403 | ✅ (egen klinik) | ✅ | +| Läsa eget ärende | ✅ | ✅ (samma klinik) | ✅ | +| Uppdatera titel/beskrivning | ✅ (eget) | ✅ (samma klinik) | ✅ | +| Ändra status / tilldela VET / stänga | ❌ 403 | ✅ | ✅ | +| Släppa tilldelat ärende | ❌ | ✅ (bara egen self-assign) | ✅ | +| Kommentera eget öppet ärende | ✅ (`OWNER_MESSAGE`) | ✅ (`VET_CLINICAL_NOTE`) | ✅ | +| Se VET:ens journalanteckningar på eget ärende | ✅ | ✅ | ✅ | +| Ladda upp bilaga på eget öppet ärende | ✅ | ✅ (även stängda för arkivering) | ✅ | +| Radera bilaga | ❌ 403 | ✅ (egen uppladdning, samma klinik) | ✅ | -| Prefix | Beskrivning | -|----------------------|--------------------------------| -| `/api/clinics` | CRUD för kliniker | -| `/api/vets` | Hämta och skapa veterinärer | -| `/api/pets` | Husdjurshantering (pågående) | -| `/api/comments` | Kommentarer på ärenden | -| `/api/activity-logs` | Revisionslogg | +Policybrott kastar `ForbiddenException` → HTTP 403. --- -## Roller och behörigheter +## Säkerhet + +### Autentisering + +- Username/password (email + BCrypt-hash) verifieras via `DaoAuthenticationProvider` + `CustomUserDetailsService`. +- Vid lyckad login utfärdar `JwtService.generateToken()` en JWT med claims: `sub` (email), `userId`, `role`, `name`, ev. `clinicId`, `iat`, `exp`. +- Signering: HS256 med symmetrisk nyckel från `JWT_SECRET` (min. 32 tecken). +- Standard expiration: 24 h. + +### Hur tokenen används -Auktoriseringslogiken hanteras i `MedicalRecordPolicy`. +- Frontend sparar tokenen i `localStorage` (med "Kom ihåg mig") eller `sessionStorage`. +- Axios-interceptor lägger på `Authorization: Bearer ` på varje request. +- Backend: `JwtAuthenticationFilter` extraherar token, validerar signatur + `exp`, hämtar `User` från DB via `userId`, sätter `SecurityContextHolder`. -| Roll | Behörighet | -|---------|-------------------------------------------------------------| -| `OWNER` | Ser och hanterar enbart sina egna ärenden och husdjur | -| `VET` | Ser och hanterar ärenden knutna till sin klinik | -| `ADMIN` | Full tillgång till allt | +### Stateless -Policybrott kastar `ForbiddenException` (HTTP 403). +- Ingen server-session (`SessionCreationPolicy.STATELESS`). +- CSRF avstängt — irrelevant för Bearer-token-auth. Detta beslut är medvetet dokumenterat i `SecurityConfig`. + +### Inte i scope (idag) + +- Rate limiting / brute-force-skydd på `/api/auth/login` +- Refresh tokens / token-revokering +- Passkeys / OAuth2-social-login +- Separat audit-logg för säkerhetshändelser (failed logins, 403-försök) — domän-audit finns i `activity_log` --- ## Fillagring (MinIO/S3) -Bilagor lagras i MinIO (S3-kompatibelt). Vid applikationsstart skapas bucket automatiskt om den saknas. +Bilagor lagras i MinIO (S3-kompatibel). Vid applikationsstart skapas bucket:en om den saknas. + +**Nedladdning:** `GET /api/attachments/{id}/download` returnerar en **presigned URL** med kort livslängd. Policy (`canDownload`) körs innan URL:en genereras, så orättmätig access blockeras redan på backend-sidan. -**Konfigureras via:** `MinioConfig.java` -**Relevanta env-variabler:** `S3_ENDPOINT`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`, `S3_BUCKET`, `S3_REGION` +**Uppladdning:** multipart/form-data, MIME valideras mot en allowlist (JPG/PNG/PDF), max 10 MB. + +**Radering:** `AttachmentService.deleteAttachment()` tar bort DB-raden i en transaktion och försöker därefter radera S3-objektet. **Om S3-deletion misslyckas efter commit är objektet idag föräldralöst** — se [Pågående arbete](#pågående-och-planerade-arbeten). --- -### Att göra +## Felhantering -| Uppgift | Prioritet | -|-------------------------------------------------------|-----------| -| Admin-policy (auktorisering för adminrollen) | Hög | -| Loggning – genomgång och utökning med SLF4J | Medium | -| Tester – enhetstester för egna klasser | Hög | -| Tester – integrationstester | Hög | -| Fortsätta skapa issues och fördela ansvar | Löpande | +Global exception handling via `GlobalExceptionHandler` (`@RestControllerAdvice`). -### Klart nyligen +| Exception | HTTP | Beskrivning | +|---|---|---| +| `ResourceNotFoundException` | 404 | Resursen hittades inte | +| `ForbiddenException` | 403 | Behörighet saknas (från policy) | +| `BusinessRuleException` | 422 | Affärsregelfel (t.ex. stängt ärende, dubblett) | +| `BadCredentialsException` | 401 | Fel lösenord vid login | +| Valideringsfel (`MethodArgumentNotValid`, `ConstraintViolation`) | 400 | Ogiltiga request-fält | +| `AccessDeniedException` | 403 | Spring Securitys egen (mappas separat) | +| Okänt undantag | 500 | Fallback | -- Borttaget dubblett-exceptions-paket -- MinIO-konfiguration ersätter `System.out` med SLF4J-loggning -- Klinik-controller refaktorering -- Pet service policy +Alla felsvar är JSON med `status`, `error`, `message`. --- ## Testning -> Tester är ännu inte implementerade – detta är ett prioriterat nästa steg. +**~500 tester** körs via `./mvnw test`. Fördelning: -Planen är: -1. **Enhetstester** – varje utvecklare skriver tester för sina egna klasser -2. **Integrationstester** – tester mot riktig databas (ingen mocking av DB) +- **Policy-tester** — alla sex policy-klasser har uttömmande rollmatriser +- **Service-tester** (Mockito) — alla service-klasser +- **Controller-tester** (`@WebMvcTest` + MockMvc + `SecurityMockMvcRequestPostProcessors`) +- **Entity-tester** — invariants och relations +- **Integrationstester** — `VetIntegrationTest`, `ClinicIntegrationTest`, `ActivityLogIntegrationTest` (H2 in-memory med PostgreSQL-kompatibelt läge) -Testberoenden är redan konfigurerade i `pom.xml`: -- `spring-boot-starter-data-jpa-test` -- `spring-boot-starter-webmvc-test` -- `spring-boot-starter-thymeleaf-test` +Täckning rapporteras av JaCoCo (`target/site/jacoco/index.html` efter `./mvnw verify`). + +Ej i scope idag: end-to-end-tester (Playwright/Cypress), mutationstestning, Testcontainers mot riktig Postgres. --- ## CI/CD -> CI/CD-pipeline är under uppsättning. +`.github/workflows/ci.yml` körs på varje push till `main` och varje PR mot `main`. + +Pipeline: + +1. Checkout +2. Setup JDK (Temurin 25) +3. Cache `~/.m2` +4. `mvn clean verify` — kompilering + alla tester +5. Upload JAR-artefakt (endast vid push till `main`) -Planerat: -- GitHub Actions workflow för bygge och test vid PR -- Automatisk körning av `./mvnw verify` +Inget automatiserat deploy-steg (deploy görs manuellt idag). --- -## Felhantering +## Pågående och planerade arbeten + +### 🚧 Flyway-migration + +Databasmigration sker idag via `spring.sql.init` som kör `schema.sql` vid start. Schemat är skrivet idempotent (`CREATE TABLE IF NOT EXISTS` + `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`) för att klara återkörning, men det skalar inte för många iterationer och historiken är inte spårbar i databasen. + +**Plan:** +- Introducera Flyway med versionerade migrationer (`V1__init.sql`, `V2__add_comment_type.sql`, `V3__add_attachment_description.sql` osv.) +- Baseline mot befintligt schema så att existerande databaser kan adoptera Flyway utan att förlora data +- `flyway_schema_history`-tabell ger tydlig körnings-logg +- Ta bort `spring.sql.init`-mekanismen när Flyway är live + +### 🚧 Durable retry för S3-radering + +**Problem:** När `AttachmentService.deleteAttachment()` tar bort DB-raden lyckas — men det efterföljande MinIO-anropet misslyckas (nätverksfel, transient fel) — loggas felet och DB-transaktionen är redan commit:ad. Filen ligger då föräldralös i MinIO med ingen automatisk återhämtning. + +**Plan:** +- Införa en `pending_deletions`-tabell som skriver ned `s3Key` i samma transaktion som metadata-raderingen +- En bakgrundsjob (Spring `@Scheduled`) plockar rader med retry-räknare och exponential backoff +- Lyckad deletion → radera raden. Misslyckad efter N försök → flagga för manuell översyn +- Metrik/larm på backlog-storlek -Global felhantering via `GlobalExceptionHandler` (`@RestControllerAdvice`). +### ♻️ Övrigt på backloggen -| Exception | HTTP-status | Beskrivning | -|----------------------------|-------------|------------------------------------| -| `ResourceNotFoundException`| 404 | Resursen hittades inte | -| `ForbiddenException` | 403 | Behörighet saknas | -| `BusinessRuleException` | 400 | Affärsregelfel | -| Valideringsfel | 400 | Ogiltiga request-fält | -| Övriga fel | 500 | Okänt serverfel | +| Område | Beskrivning | +|---|---| +| Rate limiting | Skydd mot brute-force på `/api/auth/login` (Bucket4j eller Spring Security:s `loginAttempts`) | +| Audit-logg för säkerhetshändelser | Separat från `activity_log` — failed logins, 403-försök, roll-ändringar | +| E2E-tester | Playwright eller Cypress mot staging-miljö | +| Testcontainers | Byta H2 mot riktig PostgreSQL i integrationstester | +| i18n | Flera språk i frontend (idag bara svenska hårdkodat) | +| Statisk analys | Spotless / Checkstyle för backend | +| CD-pipeline | Automatiskt deploy till staging vid merge till main | From e36149ebf2d35eb852222cc3f806e52ceea88be0 Mon Sep 17 00:00:00 2001 From: Annika Holmqvist Date: Mon, 27 Apr 2026 09:29:46 +0200 Subject: [PATCH 2/2] docs: simplify and reorganize README for clarity, update tech stack, setup, and API details --- README.md | 458 ++++++++++++++++++++++-------------------------------- 1 file changed, 190 insertions(+), 268 deletions(-) diff --git a/README.md b/README.md index a106ac50..295daf63 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,23 @@ -# Vet1177 — Digital veterinärvård +# Vet1177 – Veterinärjournalsystem -Ett webbaserat journalhanteringssystem för veterinärkliniker, inspirerat av 1177.se. Djurägare registrerar ärenden för sina djur och kommunicerar med kliniken. Veterinärer tar ärenden, dokumenterar journaler och bifoga filer. Administratörer hanterar kliniker och användare. - -Projektet är fullstack: Spring Boot-backend med PostgreSQL + MinIO, och React-frontend. +Ett webbaserat journalhanteringssystem för veterinärkliniker. Husdjursägare kan skapa ärenden, veterinärer kan hantera journaler och bifoga filer, och administratörer har full tillgång till systemet. --- ## Innehåll -- [Arkitektur](#arkitektur) - [Tech-stack](#tech-stack) - [Kom igång](#kom-igång) - [Miljövariabler](#miljövariabler) - [Projektstruktur](#projektstruktur) -- [Domänmodell](#domänmodell) -- [API](#api) +- [API-endpoints](#api-endpoints) - [Roller och behörigheter](#roller-och-behörigheter) - [Säkerhet](#säkerhet) +- [Databasmigration (Flyway)](#databasmigration-flyway) - [Fillagring (MinIO/S3)](#fillagring-minios3) -- [Felhantering](#felhantering) - [Testning](#testning) - [CI/CD](#cicd) -- [Pågående och planerade arbeten](#pågående-och-planerade-arbeten) - ---- - -## Arkitektur - -``` -┌─────────────────────┐ JWT via Authorization header ┌─────────────────────┐ -│ │ ─────────────────────────────────────▶ │ │ -│ React SPA │ │ Spring Boot │ -│ (Vite + Tailwind) │ ◀───────────────────────────────────── │ REST API │ -│ │ JSON responses │ │ -└─────────────────────┘ └──────────┬──────────┘ - │ - ┌────────────────────┼────────────────────┐ - ▼ ▼ ▼ - ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ - │ PostgreSQL │ │ MinIO / S3 │ │ JWT-signed │ - │ (schema.sql)│ │ (attachments)│ │ tokens │ - └─────────────┘ └──────────────┘ └──────────────┘ -``` - -- Backend är en stateless REST-tjänst. Inga server-sessioner — identiteten bärs i JWT. -- Frontend är en separat SPA som bara pratar med backend via REST. -- Fillagring sker utanför relationsdatabasen: DB håller metadata, objekt-binärer ligger i MinIO (S3-kompatibel). -- Auktorisering är tvåstegs: grovmaskigt rollfilter på URL-nivå i Spring Security, finkornig ägarskap/kliniktillhörighet i dedikerade policy-klasser. +- [Felhantering](#felhantering) --- @@ -54,33 +25,27 @@ Projektet är fullstack: Spring Boot-backend med PostgreSQL + MinIO, och React-f ### Backend -| Kategori | Teknologi | -|---|---| -| Språk | Java 24 | -| Ramverk | Spring Boot 4.0.4 (MVC, Data JPA, Validation) | -| Säkerhet | Spring Security 7 + `spring-boot-starter-oauth2-resource-server` (JWT-validering) | -| Databas | PostgreSQL 15 | -| ORM | Hibernate (via Spring Data JPA) | -| Migration | `schema.sql` via `spring.sql.init` (Flyway planerad — se [Pågående arbete](#pågående-och-planerade-arbeten)) | -| Fillagring | MinIO (S3-kompatibel) via AWS SDK for Java v2 | -| Serialisering | Jackson Databind | -| Loggning | SLF4J | -| Test | JUnit 5, Mockito, AssertJ, Spring Security Test, `@WebMvcTest` | -| Coverage | JaCoCo | -| Byggsystem | Maven (med wrapper `mvnw`) | -| Containerisering | Docker Compose (Postgres + MinIO) | +| Kategori | Teknologi | +|-----------------|------------------------------------| +| Språk | Java 24 | +| Ramverk | Spring Boot 4.0.4 | +| Databas | PostgreSQL 15 | +| ORM | Spring Data JPA / Hibernate | +| Migration | Flyway (versionerade migrationer) | +| Säkerhet | Spring Security 6 | +| Fillagring | MinIO (S3-kompatibel) | +| Loggning | SLF4J | +| Byggsystem | Maven | +| Containerisering| Docker / Docker Compose | ### Frontend -| Kategori | Teknologi | -|---|---| -| Ramverk | React 19 | -| Build | Vite 8 | -| Styling | Tailwind CSS 3 | -| HTTP | Axios (med request/response-interceptors för JWT och 401/403-hantering) | -| JWT parsing | `jwt-decode` | -| Ikoner | Lucide React | -| Linting | ESLint 9 | +| Kategori | Teknologi | +|-----------------|------------------------------------| +| Ramverk | React 19 | +| Byggverktyg | Vite 8 | +| Styling | Tailwind CSS 3 | +| HTTP-klient | Axios | --- @@ -89,9 +54,9 @@ Projektet är fullstack: Spring Boot-backend med PostgreSQL + MinIO, och React-f ### Förutsättningar - Java 24 -- Maven (wrapper finns i repot) +- Maven - Docker & Docker Compose -- Node.js 18+ och npm (för frontend) +- Node.js (för frontend) ### Installation @@ -116,14 +81,16 @@ docker compose up -d ```bash ./mvnw spring-boot:run ``` + Backenden startar på `http://localhost:8080`. -**5. Starta frontend (i ny terminal)** +**5. Starta frontend** ```bash cd frontend npm install npm run dev ``` + Frontenden startar på `http://localhost:5173`. > MinIO-konsolen nås på `http://localhost:9001` (använd dina `MINIO_ROOT_USER`/`MINIO_ROOT_PASSWORD`). @@ -132,137 +99,112 @@ Frontenden startar på `http://localhost:5173`. ## Miljövariabler -| Variabel | Beskrivning | Exempel | -|---|---|---| -| `DB_URL` | PostgreSQL JDBC URL | `jdbc:postgresql://localhost:5432/vet1177` | -| `DB_USERNAME` | Databasanvändare | `postgres` | -| `DB_PASSWORD` | Databaslösenord | – | -| `DB_NAME` | Databasnamn | `vet1177` | -| `S3_ENDPOINT` | MinIO/S3-URL | `http://localhost:9000` | -| `S3_ACCESS_KEY` | S3 access key | – | -| `S3_SECRET_KEY` | S3 secret key | – | -| `S3_BUCKET` | Bucket-namn för bilagor | `vet1177-attachments` | -| `S3_REGION` | S3-region | `eu-north-1` | -| `MINIO_ROOT_USER` | MinIO admin-användare | – | -| `MINIO_ROOT_PASSWORD` | MinIO admin-lösenord | – | -| `JWT_SECRET` | Hemlig nyckel för JWT-signering (min. 32 tecken) | – | - -`jwt.expiration-ms` konfigureras i `application.properties` (default 24 h). Frontenden läser `VITE_API_BASE_URL` — defaultar till `http://localhost:8080/api`. +| Variabel | Beskrivning | Exempel | +|--------------------|----------------------------------|--------------------------------------------| +| `DB_URL` | PostgreSQL JDBC URL | `jdbc:postgresql://localhost:5432/vet1177` | +| `DB_USERNAME` | Databasanvändare | `postgres` | +| `DB_PASSWORD` | Databaslösenord | – | +| `DB_NAME` | Databasnamn | `vet1177` | +| `S3_ENDPOINT` | MinIO/S3 URL | `http://localhost:9000` | +| `S3_ACCESS_KEY` | S3 access key | – | +| `S3_SECRET_KEY` | S3 secret key | – | +| `S3_BUCKET` | Bucket-namn för bilagor | `vet1177-attachments` | +| `S3_REGION` | S3-region | `eu-north-1` | +| `MINIO_ROOT_USER` | MinIO admin-användare | – | +| `MINIO_ROOT_PASSWORD` | MinIO admin-lösenord | – | --- ## Projektstruktur ``` -. -├── src/main/java/org/example/vet1177/ -│ ├── config/ # AWS/MinIO-konfiguration -│ ├── controller/ # REST-controllers (Medical, Attachment, Comment, Pet, Clinic, Vet, User, ActivityLog) -│ ├── dto/ -│ │ ├── request/ # Inkommande DTO:er med Bean Validation -│ │ └── response/ # Utgående DTO:er -│ ├── entities/ # JPA-entiteter + enums (Role, RecordStatus, ActivityType, CommentType) -│ ├── exception/ # Custom exceptions + GlobalExceptionHandler -│ ├── policy/ # Auktoriseringsregler per domänobjekt (6 policies) -│ ├── repository/ # Spring Data JPA-repositories -│ ├── security/ # JwtService, JwtAuthenticationFilter, CustomUserDetailsService, -│ │ # SecurityConfig, DevSecurityConfig, AuthService, AuthController -│ └── services/ # Affärslogik -├── src/main/resources/ -│ ├── schema.sql # Tabelldefinitioner (idempotent med IF NOT EXISTS) -│ ├── data.sql # Seed-data för dev -│ ├── application.properties -│ └── application-dev.properties -├── src/test/java/... # 500+ tester: policy, service, controller, entity, integration -├── frontend/ -│ ├── src/ -│ │ ├── pages/ # CaseDetail, OwnerDashboard, VetDashboard, AdminDashboard, Login, Register, PetDetail, CreateCase -│ │ ├── components/ # Layout, PetForm, PetList, admin/* -│ │ ├── services/api.jsx # Axios-instans med interceptors -│ │ └── utils/statusHelper.js # Central svensk-mappning för status + ActivityType -│ └── package.json -├── docker-compose.yml # Postgres 15 + MinIO -├── .github/workflows/ci.yml # GitHub Actions -├── API.md # Detaljerad API-dokumentation -└── pom.xml +src/main/java/org/example/vet1177/ +├── config/ # Spring-konfiguration (MinIO/S3) +├── controller/ # REST-controllers +├── dto/ +│ ├── request/ # Inkommande request-objekt (med validering) +│ └── response/ # Utgående response-objekt +├── entities/ # JPA-entiteter +├── exception/ # Anpassade undantag och global felhanterare +├── policy/ # Auktoriseringslogik per roll +├── repository/ # Spring Data JPA-repositories +├── security/ # Spring Security-konfiguration + auth +│ └── auth/dto/ # Auth-relaterade DTOs +└── services/ # Affärslogik + +frontend/src/ +├── components/ # Återanvändbara UI-komponenter +├── pages/ # Sidkomponenter per roll (Owner, Vet, Admin) +├── services/ # API-anrop (axios) +└── utils/ # Hjälpfunktioner ``` ---- - -## Domänmodell - ### Entiteter -| Entitet | Beskrivning | -|---|---| -| `User` | Användare med roll (`OWNER`, `VET`, `ADMIN`), ev. kopplad till en klinik | -| `Vet` | Veterinärprofil — kompletterar `User` med licens-ID och specialisering | -| `Clinic` | Klinik med adress, telefon, e-post | -| `Pet` | Djur kopplat till en ägare | -| `MedicalRecord` | Ärende/journal — kärnenhet. Har status, ägare, klinik, ev. tilldelad VET | -| `Comment` | Meddelande på ett ärende, typad som `OWNER_MESSAGE` eller `VET_CLINICAL_NOTE` | -| `Attachment` | Metadata för en bifogad fil — binären ligger i MinIO | -| `ActivityLog` | Revisionsspår av händelser på ärenden (separat från `medical_record`-tabellen) | +| Entitet | Beskrivning | +|-----------------|----------------------------------------------------------| +| `User` | Användare med roll: `OWNER`, `VET`, `ADMIN` | +| `Vet` | Utökar User med licensnummer och specialisering | +| `Clinic` | Veterinärklinik | +| `Pet` | Husdjur knutet till en ägare | +| `MedicalRecord` | Ärende/journal – kärnan i systemet | +| `Comment` | Kommentar på ett ärende | +| `Attachment` | Bifogad fil lagrad i MinIO/S3 | +| `ActivityLog` | Revisionslogg över händelser på ett ärende | ### Ärendestatus Status är en enum (`RecordStatus`): -- `OPEN` — nytt ärende, inte tilldelat +- `OPEN` — nytt ärende, ingen handläggare tilldelad - `IN_PROGRESS` — handläggning pågår (sätts automatiskt vid `assignVet`) - `AWAITING_INFO` — väntar på komplettering från ägaren -- `CLOSED` — avslutat (slutstatus, kräver slutnotering) +- `CLOSED` — avslutat (slutstatus, kräver slutnotering vid stängning) -VET kan hoppa mellan `OPEN`, `IN_PROGRESS` och `AWAITING_INFO`. Stängning sker via en dedikerad endpoint som kräver en slutnotering. - -### Aktivitetstyper - -`ActivityType`-enumet används i revisionsspåret: `CASE_CREATED`, `STATUS_CHANGED`, `COMMENT_ADDED`, `ASSIGNED`, `UNASSIGNED`, `UPDATED`. +VET kan fritt växla mellan `OPEN`, `IN_PROGRESS` och `AWAITING_INFO` via `PUT /{id}/status`. Stängning sker bara via dedikerad `PUT /{id}/close`. --- -## API - -Bas-URL: `/api`. Alla endpoints kräver JWT i `Authorization: Bearer ` utom `/api/auth/**` och `GET /api/clinics/**`. - -Fullständig dokumentation finns i [`API.md`](./API.md). - -### Autentisering — `/api/auth` - -| Metod | Sökväg | Beskrivning | -|---|---|---| -| POST | `/login` | Logga in. Returnerar JWT + userinfo | -| POST | `/register` | Registrera ny OWNER. Returnerar JWT + userinfo | - -### Journaler — `/api/medical-records` - -| Metod | Sökväg | Beskrivning | -|---|---|---| -| POST | `/` | Skapa nytt ärende | -| GET | `/{id}` | Hämta ärende | -| GET | `/my-records` | Inloggad ägares ärenden | -| GET | `/my-assigned` | Inloggad veterinärs tilldelade ärenden | -| GET | `/owner/{ownerId}` | Ärenden för en ägare | -| GET | `/pet/{petId}` | Ärenden för ett djur | -| GET | `/clinic/{clinicId}` | Ärenden på en klinik | -| GET | `/clinic/{clinicId}/status/{status}` | Filtrera klinik + status | -| PUT | `/{id}` | Uppdatera titel/beskrivning (OWNER på eget, VET/ADMIN) | -| PUT | `/{id}/assign-vet` | Tilldela veterinär | -| PUT | `/{id}/unassign-vet` | Tilldelad VET släpper ärendet | -| PUT | `/{id}/status` | Ändra status (VET/ADMIN) | -| PUT | `/{id}/close` | Stäng ärende (VET/ADMIN) | - -### Övriga prefix - -| Prefix | Beskrivning | -|---|---| -| `/api/pets` | CRUD för djur | -| `/api/attachments` | Ladda upp, lista, hämta (med presigned URL), radera bilagor | -| `/api/comments` | Kommentera ärenden | -| `/api/activity-logs` | Revisionsspår per ärende / globalt (ADMIN) | -| `/api/clinics` | CRUD för kliniker (ADMIN) | -| `/api/users` | User management (ADMIN) | -| `/api/vets` | Veterinärprofiler | +## API-endpoints + +Bas-URL: `/api` + +### Autentisering – `/api/auth` + +| Metod | Sökväg | Beskrivning | +|-------|-------------|-------------------| +| POST | `/login` | Logga in (JWT) | +| POST | `/register` | Registrera ägare | + +### Journaler – `/api/medical-records` + +| Metod | Sökväg | Beskrivning | +|--------|--------------------------------------|------------------------------------| +| POST | `/` | Skapa nytt ärende | +| GET | `/{id}` | Hämta ärende via ID | +| GET | `/my-records` | Inloggad ägares ärenden | +| GET | `/my-assigned` | Inloggad veterinärs tilldelade | +| GET | `/owner/{ownerId}` | Ärenden för en ägare | +| GET | `/pet/{petId}` | Ärenden för ett husdjur | +| GET | `/clinic/{clinicId}` | Ärenden på en klinik | +| GET | `/clinic/{clinicId}/status/{status}` | Filtrera på klinik och status | +| PUT | `/{id}` | Uppdatera ärende | +| PUT | `/{id}/assign-vet` | Tilldela veterinär | +| PUT | `/{id}/unassign-vet` | Tilldelad VET släpper ärendet | +| PUT | `/{id}/status` | Uppdatera status | +| PUT | `/{id}/close` | Stäng ärende | + +### Övriga endpoints + +| Prefix | Beskrivning | +|----------------------|--------------------------------------| +| `/api/users` | CRUD för användare (admin) | +| `/api/clinics` | CRUD för kliniker | +| `/api/vets` | Hämta, skapa och uppdatera veterinärer | +| `/api/pets` | Husdjurshantering | +| `/api/comments` | Kommentarer på ärenden | +| `/api/attachments` | Bilagor (uppladdning, nedladdning) | +| `/api/activity-logs` | Revisionslogg | --- @@ -270,9 +212,8 @@ Fullständig dokumentation finns i [`API.md`](./API.md). Auktoriseringen sker i två lager: -**Lager 1 — URL-nivå** (`SecurityConfig`): grovmaskigt rollfilter via `requestMatchers(...).hasAnyRole(...)`. T.ex. `DELETE /api/attachments/**` kräver `VET` eller `ADMIN`. - -**Lager 2 — Policy-klasser** (ett per domänobjekt): finkornig ägarskap / kliniktillhörighet / statuschecks. Kallas från service/controller innan mutationer. +**Lager 1 — URL-nivå** i `SecurityConfig` (`requestMatchers(...).hasAnyRole(...)`) — grovmaskigt rollfilter. +**Lager 2 — Policy-klasser** — finkornig ägarskap, kliniktillhörighet och statuschecks. Kallas från service eller controller innan mutationer. | Policy-klass | Ansvar | |---|---| @@ -283,22 +224,13 @@ Auktoriseringen sker i två lager: | `ActivityLogPolicy` | canView — endast ADMIN eller inblandade parter | | `AdminPolicy` | Gate för ADMIN-only operationer | -### Rollmatris (sammanfattning) - -| Handling | OWNER | VET | ADMIN | -|---|---|---|---| -| Skapa ärende för eget djur | ✅ | ✅ (egen klinik) | ✅ | -| Skapa ärende för annans djur | ❌ 403 | ✅ (egen klinik) | ✅ | -| Läsa eget ärende | ✅ | ✅ (samma klinik) | ✅ | -| Uppdatera titel/beskrivning | ✅ (eget) | ✅ (samma klinik) | ✅ | -| Ändra status / tilldela VET / stänga | ❌ 403 | ✅ | ✅ | -| Släppa tilldelat ärende | ❌ | ✅ (bara egen self-assign) | ✅ | -| Kommentera eget öppet ärende | ✅ (`OWNER_MESSAGE`) | ✅ (`VET_CLINICAL_NOTE`) | ✅ | -| Se VET:ens journalanteckningar på eget ärende | ✅ | ✅ | ✅ | -| Ladda upp bilaga på eget öppet ärende | ✅ | ✅ (även stängda för arkivering) | ✅ | -| Radera bilaga | ❌ 403 | ✅ (egen uppladdning, samma klinik) | ✅ | +| Roll | Behörighet | +|---------|-------------------------------------------------------------| +| `OWNER` | Ser och hanterar enbart sina egna ärenden och husdjur | +| `VET` | Ser och hanterar ärenden knutna till sin klinik | +| `ADMIN` | Full tillgång till allt | -Policybrott kastar `ForbiddenException` → HTTP 403. +Policybrott kastar `ForbiddenException` (HTTP 403). --- @@ -307,122 +239,112 @@ Policybrott kastar `ForbiddenException` → HTTP 403. ### Autentisering - Username/password (email + BCrypt-hash) verifieras via `DaoAuthenticationProvider` + `CustomUserDetailsService`. -- Vid lyckad login utfärdar `JwtService.generateToken()` en JWT med claims: `sub` (email), `userId`, `role`, `name`, ev. `clinicId`, `iat`, `exp`. -- Signering: HS256 med symmetrisk nyckel från `JWT_SECRET` (min. 32 tecken). -- Standard expiration: 24 h. +- Vid lyckad login utfärdar `JwtService.generateToken()` en signerad JWT (HS256, hemlig nyckel via `JWT_SECRET`). +- Frontend sparar tokenen i `localStorage`/`sessionStorage` och bifogar den som `Authorization: Bearer ` på efterföljande requests. -### Hur tokenen används +### JWT-claims -- Frontend sparar tokenen i `localStorage` (med "Kom ihåg mig") eller `sessionStorage`. -- Axios-interceptor lägger på `Authorization: Bearer ` på varje request. -- Backend: `JwtAuthenticationFilter` extraherar token, validerar signatur + `exp`, hämtar `User` från DB via `userId`, sätter `SecurityContextHolder`. +Tokenen bär följande claims: -### Stateless +| Claim | Beskrivning | +|------------|-----------------------------------------------------------------| +| `sub` | Användarens email (subject) | +| `userId` | UUID — backend slår upp `User` i DB vid varje request | +| `role` | `ROLE_OWNER` / `ROLE_VET` / `ROLE_ADMIN` — driver URL-filter | +| `name` | Användarens namn (för UI-visning) | +| `clinicId` | Klinik-UUID (sätts endast för VET) — används i policy-checks | +| `iat` | Issued-at timestamp | +| `exp` | Expiration (default 24 h) | -- Ingen server-session (`SessionCreationPolicy.STATELESS`). -- CSRF avstängt — irrelevant för Bearer-token-auth. Detta beslut är medvetet dokumenterat i `SecurityConfig`. +`JwtAuthenticationFilter` validerar signaturen, kontrollerar `exp`, och hydratiserar `User`-objektet från DB innan controllers nås. -### Inte i scope (idag) +### Stateless -- Rate limiting / brute-force-skydd på `/api/auth/login` -- Refresh tokens / token-revokering -- Passkeys / OAuth2-social-login -- Separat audit-logg för säkerhetshändelser (failed logins, 403-försök) — domän-audit finns i `activity_log` +- Ingen server-session (`SessionCreationPolicy.STATELESS`). +- CSRF-skydd avstängt — irrelevant för Bearer-token-baserad auth. --- -## Fillagring (MinIO/S3) - -Bilagor lagras i MinIO (S3-kompatibel). Vid applikationsstart skapas bucket:en om den saknas. +## Databasmigration (Flyway) -**Nedladdning:** `GET /api/attachments/{id}/download` returnerar en **presigned URL** med kort livslängd. Policy (`canDownload`) körs innan URL:en genereras, så orättmätig access blockeras redan på backend-sidan. +Schemat hanteras av Flyway-migrationer i `src/main/resources/db/migration/`: -**Uppladdning:** multipart/form-data, MIME valideras mot en allowlist (JPG/PNG/PDF), max 10 MB. +| Migration | Beskrivning | +|---|---| +| `V1__initial_schema.sql` | Grundläggande tabeller (users, clinics, pets, medical_record, comments, attachments, activity_log) | +| `V2__init_orphaned_table.sql` | Tabellen `orphaned_s3_objects` för durable retry (se [Fillagring](#fillagring-minios3)) | +| `dev/V3__insert_demo_data.sql` | Demo-data, **endast** i dev-profilen (`spring.flyway.locations` inkluderar `dev/` för dev-profilen) | -**Radering:** `AttachmentService.deleteAttachment()` tar bort DB-raden i en transaktion och försöker därefter radera S3-objektet. **Om S3-deletion misslyckas efter commit är objektet idag föräldralöst** — se [Pågående arbete](#pågående-och-planerade-arbeten). +`flyway_schema_history`-tabellen i databasen håller koll på vilka migrationer som körts. Nya migrationer läggs till med nästkommande versionsnummer (`V3__`, `V4__` ...). --- -## Felhantering +## Fillagring (MinIO/S3) -Global exception handling via `GlobalExceptionHandler` (`@RestControllerAdvice`). +Bilagor lagras i MinIO (S3-kompatibelt). Vid applikationsstart skapas bucket automatiskt om den saknas. -| Exception | HTTP | Beskrivning | -|---|---|---| -| `ResourceNotFoundException` | 404 | Resursen hittades inte | -| `ForbiddenException` | 403 | Behörighet saknas (från policy) | -| `BusinessRuleException` | 422 | Affärsregelfel (t.ex. stängt ärende, dubblett) | -| `BadCredentialsException` | 401 | Fel lösenord vid login | -| Valideringsfel (`MethodArgumentNotValid`, `ConstraintViolation`) | 400 | Ogiltiga request-fält | -| `AccessDeniedException` | 403 | Spring Securitys egen (mappas separat) | -| Okänt undantag | 500 | Fallback | +**Konfigureras via:** `MinioConfig.java` +**Relevanta env-variabler:** `S3_ENDPOINT`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`, `S3_BUCKET`, `S3_REGION` -Alla felsvar är JSON med `status`, `error`, `message`. +### Durable retry vid S3-fel (orphan-cleanup) ---- +Vissa S3-operationer kan misslyckas efter att DB-transaktionen redan har committats — då skulle binären annars ligga föräldralös i MinIO utan automatisk återhämtning. För att undvika detta finns en bakgrundsmekanism som garanterar att alla föräldralösa objekt till slut städas upp. -## Testning +**Komponenter:** + +| Klass | Ansvar | +|---|---| +| `OrphanedS3Object` | JPA-entitet för tabellen `orphaned_s3_objects` (`s3_key`, `s3_bucket`, `retry_count`, `last_attempt_at`, `last_error`) | +| `OrphanedS3Enqueuer` | Lägger en S3-nyckel i kön. Kör i `Propagation.REQUIRES_NEW` så raden persisteras även om huvudtransaktionen rullar tillbaka. Idempotent (find-or-create på `s3_key`) | +| `OrphanedS3Processor` | Försöker radera ett objekt i sin egen `REQUIRES_NEW`-transaktion. Lyckat → ta bort kö-raden. Fel → öka `retry_count`, spara felmeddelande | +| `OrphanedS3CleanupWorker` | `@Scheduled(fixedDelay = 600000)` (10 min). Plockar upp till 20 rader åt gången med `NULLS FIRST`-ordning så att nya orphans prioriteras före retries | -**~500 tester** körs via `./mvnw test`. Fördelning: +**Två triggar för enqueue:** -- **Policy-tester** — alla sex policy-klasser har uttömmande rollmatriser -- **Service-tester** (Mockito) — alla service-klasser -- **Controller-tester** (`@WebMvcTest` + MockMvc + `SecurityMockMvcRequestPostProcessors`) -- **Entity-tester** — invariants och relations -- **Integrationstester** — `VetIntegrationTest`, `ClinicIntegrationTest`, `ActivityLogIntegrationTest` (H2 in-memory med PostgreSQL-kompatibelt läge) +1. **Upload-cleanup** — om DB-persistens misslyckas efter S3-uppladdning så försöker `AttachmentService` radera direkt; om även det misslyckas läggs nyckeln i kön +2. **Delete efter commit** — `AttachmentService.deleteAttachment` registrerar en `TransactionSynchronization.afterCommit`-callback. Misslyckas S3-anropet där → läggs i kön -Täckning rapporteras av JaCoCo (`target/site/jacoco/index.html` efter `./mvnw verify`). +**Permanent fail:** Om `retry_count >= MAX_RETRIES` (10) slutar workern försöka och loggar `ALERT`-rad så att operatör kan städa manuellt. Objektet ligger kvar i `orphaned_s3_objects`-tabellen för spårbarhet. -Ej i scope idag: end-to-end-tester (Playwright/Cypress), mutationstestning, Testcontainers mot riktig Postgres. +**Migration:** Tabellen skapas via Flyway-migration `V2__init_orphaned_table.sql`. --- -## CI/CD +## Testning -`.github/workflows/ci.yml` körs på varje push till `main` och varje PR mot `main`. +Tester är implementerade med JUnit 5 och Mockito. -Pipeline: +```bash +./mvnw test +``` -1. Checkout -2. Setup JDK (Temurin 25) -3. Cache `~/.m2` -4. `mvn clean verify` — kompilering + alla tester -5. Upload JAR-artefakt (endast vid push till `main`) +**Enhetstester** – finns under `src/test/java/org/example/vet1177/services/` och `policy/`, testar affärslogik och auktorisering med mockade beroenden. -Inget automatiserat deploy-steg (deploy görs manuellt idag). +**Integrationstester** – finns under `src/test/java/org/example/vet1177/integration/`, testar mot riktig databas: +- `ClinicIntegrationTest` +- `ActivityLogIntegrationTest` +- `VetIntegrationTest` --- -## Pågående och planerade arbeten - -### 🚧 Flyway-migration - -Databasmigration sker idag via `spring.sql.init` som kör `schema.sql` vid start. Schemat är skrivet idempotent (`CREATE TABLE IF NOT EXISTS` + `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`) för att klara återkörning, men det skalar inte för många iterationer och historiken är inte spårbar i databasen. +## CI/CD -**Plan:** -- Introducera Flyway med versionerade migrationer (`V1__init.sql`, `V2__add_comment_type.sql`, `V3__add_attachment_description.sql` osv.) -- Baseline mot befintligt schema så att existerande databaser kan adoptera Flyway utan att förlora data -- `flyway_schema_history`-tabell ger tydlig körnings-logg -- Ta bort `spring.sql.init`-mekanismen när Flyway är live +GitHub Actions kör bygge och tester automatiskt vid PR mot `main`. -### 🚧 Durable retry för S3-radering +- Workflow: `.github/workflows/` +- Kör `./mvnw verify` vid varje PR -**Problem:** När `AttachmentService.deleteAttachment()` tar bort DB-raden lyckas — men det efterföljande MinIO-anropet misslyckas (nätverksfel, transient fel) — loggas felet och DB-transaktionen är redan commit:ad. Filen ligger då föräldralös i MinIO med ingen automatisk återhämtning. +--- -**Plan:** -- Införa en `pending_deletions`-tabell som skriver ned `s3Key` i samma transaktion som metadata-raderingen -- En bakgrundsjob (Spring `@Scheduled`) plockar rader med retry-räknare och exponential backoff -- Lyckad deletion → radera raden. Misslyckad efter N försök → flagga för manuell översyn -- Metrik/larm på backlog-storlek +## Felhantering -### ♻️ Övrigt på backloggen +Global felhantering via `GlobalExceptionHandler` (`@RestControllerAdvice`). -| Område | Beskrivning | -|---|---| -| Rate limiting | Skydd mot brute-force på `/api/auth/login` (Bucket4j eller Spring Security:s `loginAttempts`) | -| Audit-logg för säkerhetshändelser | Separat från `activity_log` — failed logins, 403-försök, roll-ändringar | -| E2E-tester | Playwright eller Cypress mot staging-miljö | -| Testcontainers | Byta H2 mot riktig PostgreSQL i integrationstester | -| i18n | Flera språk i frontend (idag bara svenska hårdkodat) | -| Statisk analys | Spotless / Checkstyle för backend | -| CD-pipeline | Automatiskt deploy till staging vid merge till main | +| Exception | HTTP-status | Beskrivning | +|----------------------------|-------------|------------------------------------| +| `ResourceNotFoundException`| 404 | Resursen hittades inte | +| `ForbiddenException` | 403 | Behörighet saknas | +| `BusinessRuleException` | 422 | Affärsregelfel | +| `BadCredentialsException` | 401 | Fel email/lösenord vid login | +| Valideringsfel | 400 | Ogiltiga request-fält | +| Övriga fel | 500 | Okänt serverfel | \ No newline at end of file