Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 95 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Ett webbaserat journalhanteringssystem för veterinärkliniker. Husdjursägare k
- [Projektstruktur](#projektstruktur)
- [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)
- [Testning](#testning)
- [CI/CD](#cicd)
Expand All @@ -29,6 +31,7 @@ Ett webbaserat journalhanteringssystem för veterinärkliniker. Husdjursägare k
| 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 |
Expand Down Expand Up @@ -151,7 +154,14 @@ frontend/src/

### Ärendestatus

`OPEN` → `IN_PROGRESS` → `AWAITING_INFO` → `CLOSED`
Status är en enum (`RecordStatus`):

- `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 vid stängning)

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`.

---

Expand Down Expand Up @@ -180,6 +190,7 @@ Bas-URL: `/api`
| 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 |

Expand All @@ -199,7 +210,19 @@ Bas-URL: `/api`

## Roller och behörigheter

Auktoriseringslogiken hanteras av rollspecifika policy-klasser: `MedicalRecordPolicy`, `CommentPolicy` och `PetPolicy`.
Auktoriseringen sker i två lager:

**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 |
|---|---|
| `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 |

| Roll | Behörighet |
|---------|-------------------------------------------------------------|
Expand All @@ -211,13 +234,80 @@ Policybrott kastar `ForbiddenException` (HTTP 403).

---

## Säkerhet

### Autentisering

- Username/password (email + BCrypt-hash) verifieras via `DaoAuthenticationProvider` + `CustomUserDetailsService`.
- 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 <token>` på efterföljande requests.

### JWT-claims

Tokenen bär följande claims:

| 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) |

`JwtAuthenticationFilter` validerar signaturen, kontrollerar `exp`, och hydratiserar `User`-objektet från DB innan controllers nås.

### Stateless

- Ingen server-session (`SessionCreationPolicy.STATELESS`).
- CSRF-skydd avstängt — irrelevant för Bearer-token-baserad auth.

---

## Databasmigration (Flyway)

Schemat hanteras av Flyway-migrationer i `src/main/resources/db/migration/`:

| 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) |

`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__` ...).

---

## Fillagring (MinIO/S3)

Bilagor lagras i MinIO (S3-kompatibelt). Vid applikationsstart skapas bucket automatiskt om den saknas.

**Konfigureras via:** `MinioConfig.java`
**Konfigureras via:** `MinioConfig.java`
**Relevanta env-variabler:** `S3_ENDPOINT`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`, `S3_BUCKET`, `S3_REGION`

### 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.

**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 |

**Två triggar för enqueue:**

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

**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.

**Migration:** Tabellen skapas via Flyway-migration `V2__init_orphaned_table.sql`.

Comment on lines +289 to 310

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for the documented S3 orphan cleanup components in the codebase

# Search for OrphanedS3Object entity
echo "=== Searching for OrphanedS3Object entity ==="
ast-grep --pattern 'class OrphanedS3Object {
  $$$
}'

# Search for OrphanedS3Enqueuer
echo -e "\n=== Searching for OrphanedS3Enqueuer ==="
ast-grep --pattern 'class OrphanedS3Enqueuer {
  $$$
}'

# Search for OrphanedS3Processor
echo -e "\n=== Searching for OrphanedS3Processor ==="
ast-grep --pattern 'class OrphanedS3Processor {
  $$$
}'

# Search for OrphanedS3CleanupWorker with `@Scheduled` annotation
echo -e "\n=== Searching for OrphanedS3CleanupWorker ==="
rg -n -A10 '@Scheduled.*fixedDelay.*600000' --type=java

# Alternative: search for any orphaned/orphan references
echo -e "\n=== Searching for 'orphan' references in Java files ==="
rg -n -i 'orphan' --type=java -C2

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 842


Documentation describes S3 orphan cleanup components that do not exist in the codebase.

This section (lines 289-310) documents an asynchronous retry system with four components—OrphanedS3Object, OrphanedS3Enqueuer, OrphanedS3Processor, and OrphanedS3CleanupWorker—along with scheduled background processing and durable retry semantics.

However, comprehensive searches across the codebase found no trace of these classes or the documented retry pattern. The only orphan-related code found is JPA's built-in orphanRemoval = true cascade setting in MedicalRecord.java, which is unrelated to S3 cleanup.

Current S3 cleanup (as shown in AttachmentService) is synchronous only:

try {
    fileStorageService.delete(s3Key);
} catch (Exception deleteEx) {
    log.error("CRITICAL: Failed to cleanup S3 object {}!", s3Key, deleteEx);
}

Either remove this documentation section or implement the documented mechanism. If this was intentionally removed, clarify in the documentation that S3 cleanup lacks durable retry capability and relies on synchronous error handling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 289 - 310, The README documents an S3 orphan-cleanup
mechanism (OrphanedS3Object, OrphanedS3Enqueuer, OrphanedS3Processor,
OrphanedS3CleanupWorker) that doesn't exist in the codebase; update the README
to reflect reality by either removing this section or clearly stating that
durable retry is not implemented and S3 cleanup is synchronous. Concretely, edit
the documented block to (a) remove the four-class description and migration
note, or (b) replace it with a short note that AttachmentService currently
performs synchronous cleanup via fileStorageService.delete(...) inside a
try/catch and that failed deletes are logged (include the delete code snippet or
reference to AttachmentService.deleteAttachment), and mention no background
retry worker exists so operators must handle manual cleanup. Ensure the README
change references the symbols AttachmentService and fileStorageService.delete to
make the behavioral mapping explicit.

---

## Testning
Expand Down Expand Up @@ -254,6 +344,7 @@ Global felhantering via `GlobalExceptionHandler` (`@RestControllerAdvice`).
|----------------------------|-------------|------------------------------------|
| `ResourceNotFoundException`| 404 | Resursen hittades inte |
| `ForbiddenException` | 403 | Behörighet saknas |
| `BusinessRuleException` | 400 | Affärsregelfel |
| `BusinessRuleException` | 422 | Affärsregelfel |
| `BadCredentialsException` | 401 | Fel email/lösenord vid login |
| Valideringsfel | 400 | Ogiltiga request-fält |
| Övriga fel | 500 | Okänt serverfel |