feat(docs): update NEXT_STEPS, README, and TEKNISK_BESKRIVNING with new features and improvements

This commit is contained in:
Nils-Johan Gynther
2026-04-17 23:20:21 +02:00
parent 470763715d
commit 84b49bc186
3 changed files with 200 additions and 48 deletions
+160 -16
View File
@@ -82,18 +82,19 @@ docker exec recipe-db mariadb -uroot -p"LÖSENORD" recipe_app -e "SHOW TABLES;"
| **Lägg till recept** | `app/recipes/create/page.tsx` | Server component med Navigation |
| | `app/recipes/create/CreateRecipeClient.tsx` | Klientkomponent: snabbimport + metodval |
| **Skriv in recept** | `app/recipes/write/page.tsx` | Server component med Navigation |
| | `app/recipes/write/WriteRecipePage.tsx` | Markdown-baserat receptskapande (3-steg) |
| | `app/recipes/write/WriteRecipePage.tsx` | Markdown-baserat receptskapande (3-steg): Markdown-inmatning → ingrediensgranskning (produktval + portionsantal) → spara |
| **Importera från fil** | `app/recipes/import/page.tsx` | Startpunkt för fil/länk-import |
| | `app/recipes/import/ImportFilePage.tsx` | Fil-/länk-import (PDF, URL, etc) |
| **Import (flikar)** | `app/import/page.tsx` | Server component med Navigation + flikvy |
| **Matplan** | `app/matplan/page.tsx` | Matplanering (server component) |
| | `app/matplan/MealPlanClient.tsx` | Veckovy, receptval per dag, portionsjustering, inköpslista, inventariejämförelse |
| **Kvittoimport** | `app/import/page.tsx` | Server component med Navigation + flikvy |
| | `app/import/ImportTabsClient.tsx` | Klientkomponent: kvitto/recept-flikar |
| **Recipe detail** | `app/recipes/[id]/` | Enskilt recept (detaljer, redigering) |
| **Admin: Produkter** | `app/admin/products/page.tsx` | Produktadmin-panel |
| | `AdminProductList.tsx` | Lista produkter, sök, sortera |
| | `AdminProductList.tsx` | Lista produkter, sök, sortera, filter okategoriserade, bulk-select + bulk-kategorisering |
| | `EditProductForm.tsx` | Inline redigering: name, canonicalName, kategori (hierarkisk dropdown), brand, taggar |
| | `ResetProductsButton.tsx` | Knapp för att rensa all produktdata |
| | `MergePreviewForm.tsx` | Förhandsgranska merge |
| | `actions.ts` | Server actions: updateProduct, deleteProduct, resetAllProducts |
| | `actions.ts` | Server actions: updateProduct, deleteProduct, resetAllProducts, bulkSetCategory |
| **Baslager** | `app/baslager/page.tsx` | Visa och hantera baslager (server component) |
| | `AddToPantryForm.tsx` | Lägg till produkt i baslager (dropdown) |
| | `PantryList.tsx` | Visa baslager grupperat per kategori |
@@ -116,6 +117,9 @@ Alla proxy-routes läser auth-token via `auth()` (Auth.js v5) och vidarebefordra
| `/api/recipe-preview-proxy` | GET | Receptförhandsvisning |
| `/api/admin/merge-preview-proxy` | GET | Produktmerge-preview |
| `/api/receipt-import-proxy` | POST | Kvittoimport via Mistral AI |
| `/api/meal-plan-proxy` | GET, POST, DELETE | Matplanering (veckovy, upsert, ta bort) |
| `/api/meal-plan-shopping-proxy` | GET | Inköpslista för datumintervall |
| `/api/meal-plan-compare-proxy` | GET | Inventariejämförelse för datumintervall |
| `/api/user-products` | GET, POST, DELETE | Användarspecifika produkter |
### Autentisering (Auth.js v5)
@@ -203,6 +207,21 @@ backend/src/
│ ├── base.parser.ts # Abstract RecipeParser class
│ ├── ica.parser.ts # ICA.se-specifik parser (JSON-LD)
│ └── generic.parser.ts # Fallback-parser (HTML + JSON-LD)
├── meal-plan/
│ ├── meal-plan.controller.ts # GET/POST/DELETE + shopping-list + inventory-compare
│ ├── meal-plan.service.ts # Upsert, shoppingList (portionsskalad), inventoryCompare
│ ├── meal-plan.module.ts
│ └── dto/
│ └── create-meal-plan-entry.dto.ts # { date, recipeId, servings? }
├── receipt-import/
│ ├── receipt-import.controller.ts # POST /api/receipt-import (multipart)
│ ├── receipt-import.service.ts # Mistral AI-anrop, bildtolkning
│ └── dto/
│ └── parsed-receipt-item.dto.ts
├── receipt-alias/
│ ├── receipt-alias.controller.ts # CRUD /api/receipt-alias
│ ├── receipt-alias.service.ts
│ └── dto/
└── recipes/
├── recipes.controller.ts # Recept endpoints
├── recipes.service.ts # Recept + Markdown-parsing
@@ -282,6 +301,21 @@ backend/src/
- **Soft delete & restore:**
- `remove()` - Soft-delete produkt (isActive = false)
- `restore()` - Återställ borttagen produkt
- **Bulk-uppdatering:** `bulkUpdate(ids, data)` — Uppdatera ett godtyckligt antal produkter i ett enda DB-anrop (`updateMany`). Används primärt för bulk-kategorisering i admin-UI. Body: `{ ids: number[], categoryId?: number | null }`
**Matplan-API:**
- **`upsert(dto)`** — Skapar eller uppdaterar en `MealPlanEntry` för ett givet datum (unik per dag). Sparar `recipeId` och valfritt `servings`.
- **`findByRange(from, to)`** — Hämtar alla planerade dagar i ett datumintervall, inkl. receptinfo.
- **`shoppingList(from, to)`** — Aggregerar ingrediensmängder för alla planerade recept i intervallet.
- Om `entry.servings` och `recipe.servings` är satta beräknas en skala: `scale = entry.servings / recipe.servings`
- Ingrediensmängder multipliceras med skalan innan aggregering
- Returnerar lista av `{ productName, quantity, unit }`
- **`inventoryCompare(from, to)`** — Kör samma aggregering som `shoppingList` men jämför sedan varje ingrediens mot aktuellt inventarielager. Returnerar status per ingrediens: `räcker | saknas | enhetskonflikt`.
**Kvittoimport-API:**
- **`parseReceipt(file)`** — Tar emot en bildel eller PDF (max 15 MB), skickar den till Mistral AI för tolkning och returnerar en lista av kandidatprodukter med namn, kvantitet och enhet.
- Alias-matchning: före returneringen slås varje rånamn upp mot `ReceiptAlias`-tabellen och mot `Product.normalizedName`. Träffar kopplas automatiskt till rätt produkt-ID.
- Stödda MIME-typer: `image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`, `application/pdf`
---
@@ -333,6 +367,8 @@ POST /api/products/merge Slå ihop två produkter
PATCH /api/products/:id/canonical-name Uppdatera canonical name
POST /api/products/backfill-canonical Backfill canonical names (admin)
POST /api/products/reset-all Rensa all produktdata (admin)
POST /api/products/bulk-update Uppdatera flera produkter (t.ex. sätt kategori)
Body: { ids: number[], categoryId?: number | null }
```
### Kategori-endpoints
@@ -355,6 +391,30 @@ POST /api/pantry Lägg till produkt i baslagret
DELETE /api/pantry/:id Ta bort produkt från baslagret
```
### 🗓️ Matplan-endpoints
```
GET /api/meal-plan?from=YYYY-MM-DD&to=YYYY-MM-DD Lista planerade recept för datumintervall
POST /api/meal-plan Skapa eller uppdatera post (upsert per datum)
Body: { date, recipeId, servings? }
DELETE /api/meal-plan/:date Ta bort recept för ett specifikt datum
GET /api/meal-plan/shopping-list?from=...&to=... Generera inköpslista för veckan
Skalad proportionellt efter portionsjustering
GET /api/meal-plan/inventory-compare?from=...&to=... Jämför inköpslista mot inventarie
Returnerar status per ingrediens: räcker | saknas | enhetskonflikt
```
### 🧾 Kvitto-endpoints
```
POST /api/receipt-import Tolka kvittobild (JPEG, PNG, WebP, HEIC, PDF)
Multipart-form med "file"; max 15 MB
Returnerar lista av { name, quantity, unit, productId?, confidence }
GET /api/receipt-alias Lista alla kvitto-alias
POST /api/receipt-alias Skapa nytt alias (receiptName → productId)
DELETE /api/receipt-alias/:id Ta bort alias
```
---
## Datamodell (Prisma ORM)
@@ -475,14 +535,19 @@ model Recipe {
id Int @id @default(autoincrement())
name String # Receptnamn
description String? # Receptbeskrivning
servings Int? # Antal portioner receptet är dimensionerat för
imageUrl String? # URL till receptbild (valfritt)
instructions String? @db.Text # Tillagningsinstruktioner (kan vara långt, stöder Markdown)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ingredients RecipeIngredient[]
ingredients RecipeIngredient[]
mealPlanEntries MealPlanEntry[]
}
```
`servings` är grundportionsantalet — matplanen använder det för att skala ingrediensmängder om användaren anger ett avvikande portionsantal per dag.
### RecipeIngredient
```prisma
model RecipeIngredient {
@@ -512,6 +577,38 @@ model PantryItem {
}
```
### MealPlanEntry
```prisma
model MealPlanEntry {
id Int @id @default(autoincrement())
date DateTime # Datum för planerad måltid (en per dag)
recipeId Int # Foreign key till Recipe
servings Int? # Justerat portionsantal för den dagen (null = använd receptets grundvärde)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
recipe Recipe @relation(fields: [recipeId], references: [id])
@@unique([date]) # Bara ett recept per dag
}
```
**Portionsskalning:** Om `servings` är satt och skiljer sig från `recipe.servings` beräknar `shoppingList()` och `inventoryCompare()` en skala: `scale = entry.servings / recipe.servings`. Alla ingrediensmängder multipliceras med denna faktor.
### ReceiptAlias
```prisma
model ReceiptAlias {
id Int @id @default(autoincrement())
receiptName String @unique # Namn som kvittosystemet returnerar (råtext)
productId Int # FK till matchad Product
createdAt DateTime @default(now())
product Product @relation(...)
}
```
Kvitto-alias lagrar mappningar från kvittots råtext till produkt-ID. När Mistral AI returnerar t.ex. "ICA Kvarg Jordg" slås det upp mot alias-tabellen. Om träff hoppas manuell matchning över.
---
## Receptimport och receptskaping — Detaljerad arkitektur
@@ -754,6 +851,52 @@ Top 5: Max 5 förslag per ingrediens
---
## Matplanering och portionsjustering — Detaljerad arkitektur
### Syfte
Matplaneringsfunktionen låter användaren planera veckans måltider dag för dag och generera en inköpslista automatiskt. Portionsjusteringen gör det möjligt att anpassa mängden per dag utan att ändra receptet — t.ex. laga en dubbel sats en dag.
### Dataflöde
```
Användaren väljer recept + portionsantal för ett datum
→ POST /api/meal-plan { date, recipeId, servings }
→ MealPlanEntry upserteras (unik per datum)
Veckovy hämtar alla poster i intervallet
→ GET /api/meal-plan?from=...&to=...
Inköpslista genereras
→ GET /api/meal-plan/shopping-list?from=...&to=...
→ Varje ingredient × scale (entry.servings / recipe.servings, eller 1 om ej satt)
→ Aggregerat per produkt + enhet
Inventariejämförelse
→ GET /api/meal-plan/inventory-compare?from=...&to=...
→ Samma aggregering, sedan jämförs mot aktuell inventarie
→ Status: räcker | saknas | enhetskonflikt
```
### Frontend: MealPlanClient
- Veckovy renderar en kolumn per dag med aktuellt recept
- Om receptet har `servings` satt visas ett portionsinmatningsfält direkt i dagsvyn
- Avviker inmatat portionsantal från receptets grundvärde visas en återställningsknapp (↩ N portioner)
- `handleServingsChange()` POSTar direkt till backend och uppdaterar lokal state utan sidomladdning
### Portionsskalning i backend
```typescript
const scale = recipeServings && entryServings
? entryServings / recipeServings
: 1;
// Exempel: recept för 4, vill laga 6 → scale = 1.5
// 200 g pasta → 300 g pasta i inköpslistan
```
---
## Enhetskonvertering (backendsida)
### Stödda enhetstyper
@@ -882,18 +1025,19 @@ Konfigureras via `.env` eller `docker compose up`:
## Säkerhet & Utbyggbarhet
- **Ingen auth i grundutförande** (kan enkelt byggas på)
- **Validering:** Alla DTO:er valideras med class-validator
- **Felhantering:** GlobalExceptionFilter med svenska meddelanden
- **CORS:** Proxies hanteras via Next.js API routes
- **Autentisering:** JWT-baserad, 7 dagars token. Auth.js v5 (Credentials provider) i frontend. Alla backend-routes skyddas av `JwtAuthGuard` — öppna endpoints markeras med `@Public()`.
- **Middleware:** `middleware.ts` skyddar alla Next.js-routes utom `/login`, `/register` och `/api/auth`. Oinloggade användare omdirigeras automatiskt.
- **Validering:** Alla DTO:er valideras med `class-validator`. Inkommande fält i server actions bör kompletteringsvalideras (se teknisk skuld E).
- **Felhantering:** `GlobalExceptionFilter` fångar alla oupphanterade fel och returnerar svenska felmeddelanden.
- **CORS:** API-anrop proxias via Next.js API routes — klientkod når aldrig backenden direkt.
- **Filuppladdning:** Multer med `memoryStorage` och MIME-typvalidering; max 15 MB för kvittoimport.
### Möjliga utbyggnader
- Authentication (JWT, OAuth)
- Multi-user support
- Shoppinglistor
- Recept-delning
- Nutrition facts
- Allergi-tracking
- Användarroller (user / admin) — rollbaserad guard, skyddade admin-routes
- Delade recept / recept-export
- Push-notifieringar för utgångna varor
- Nutrition-baserat receptförslag
- Allergi-tracking per användare
---