Files
recipe-app/NEXT_STEPS.md
T

357 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Nästa steg
> Förslag på vad vi kan ta tag i nästa gång vi öppnar projektet.
> Se [README.md](README.md) för funktionsöversikt och [TEKNISK_BESKRIVNING.md](TEKNISK_BESKRIVNING.md) för teknisk detaljerinformation.
> Se [AI-FUNKTIONER.md](AI-FUNKTIONER.md) för planerade AI-funktioner.
---
|---|---|
| Inventorie (CRUD, konsumtion, historik) | ✅ Klart |
| Recept (skapa, visa, importera, matchning) | ✅ Klart |
| Receptredigering och borttagning (JWT-autentisering) | ✅ Klart |
| Snabbimport (URL/PDF/bild/ICA) | ✅ Klart |
| Kvittoimport (Mistral AI, OCR, alias) | ✅ Klart |
| Import-microservice (importer-api, backend-till-backend) | ✅ Klart (2026-04-30) |
| Kvittoimport — granskningsflöde i Flutter (edit, merge, destination) | ✅ Klart (2026-05-01) |
| Matplanering (veckovy, inköpslista) | ✅ Klart |
| Matplan — portionsjustering per dag | ✅ Klart |
| Matplan — inventariejämförelse (backend) | ✅ Klart |
| Matplan — inventariejämförelse (frontend-vy) | ✅ Klart (✅/⚠️/❌ integrerat i inköpslistan) |
| Baslager (lista, lägg till, ta bort) | ✅ Klart (global v1, user-scope planerad) |
| Admin: Produkter (edit, merge, duplicate, restore, reset) | ✅ Klart |
| Admin: Bulk-kategorisering | ✅ Klart |
| Receptredigering (frontend UX) | ✅ Klart |
| Receptbilder (upload URL) | ✅ Klart |
| Autentisering (JWT, Auth.js v5, User-modell) | ✅ Klart |
| Klient-side JWT via useAuthFetch() | ✅ Klart |
| SessionProvider i root layout | ✅ Klart |
| Användarprofil (firstName, lastName, email) | ✅ Klart |
| Produktkategorier — hierarkisk struktur (3 nivåer) | ✅ Klart |
| Kategori-seed (seed_all.sql, komplett träd, enda sanningskällan) | ✅ Klart |
| Kategoritilldelning i admin-UI | ✅ Klart |
| Taggning av produkter | ✅ Klart |
| Näringsvärden på produkter | ✅ Klart (schema + API) |
| Seed produktdata med kategoritilldelning | ✅ Ersatt — seed innehåller nu enbart kategorier (2026-05-02) |
| Användarspecifika produkter (UserProduct) | ✅ Klart (2026-05-02) — `Product.ownerId` obligatorisk, globala produkter borttagna |
| Användarroller (user / admin) | ✅ Klart |
| Användarhantering i admin-UI | ✅ Klart |
| Profilsida med flikar (Min profil / Användare / Databas med undertabbar) | ✅ Klart |
| Teknisk skuld — oanvända InventoryItem-fält | ✅ Klart (migration 20260418) |
| Teknisk skuld — redirect-routes städade | ✅ Klart |
| Premium-plan (isPremium på User, Free/Paid-dropdown) | ✅ Klart |
| AI-modul (AiService, Mistral-kategorisering, fallback) | ✅ Klart |
| Admin: AI-kategorisering per produkt ("Fråga AI") | ✅ Klart |
| Admin: AI-bulk-kategorisering av okategoriserade produkter | ✅ Klart |
| Produktstatus (pending / active / rejected) | ✅ Klart |
| Admin: Väntande produktförslag (pending-sida) | ✅ Klart |
| Kvittoimport — AI-kategorisuggestion för premium-användare | ✅ Klart |
| Säkerhetshuvuden — Caddy (globala headers) | ✅ Klart |
| Säkerhetshuvuden — Next.js CSP | ✅ Klart |
| Säkerhetshuvuden — NestJS Helmet (backup) | ✅ Klart (aktiveras vid nästa rebuild) |
| Buggfix: "Vad behöver jag köpa" — flimmer och scroll | ✅ Klart |
| Startsida — produktadmin-länk borttagen | ✅ Klart |
| Avancerad AI-integration (veckoplanering, receptförslag) | ❌ Planerad |
| EAN-skanning via Open Food Facts API | ❌ Planerad |
## Status — senast genomgånget: 2026-05-02
### Nyheter och förbättringar
- **Produkter user-scoped — ny databasarkitektur (2026-05-02)** — `Product.ownerId` är nu obligatorisk (non-nullable). Alla globala seed-produkter är borttagna. Varje produkt ägs av en enskild användare och raderas vid kontoradering (CASCADE). `seed_all.sql` innehåller nu enbart kategorier. Kvittoimportens matchning filtrerar på `ownerId = userId` från JWT. Se TEKNISK_BESKRIVNING.md för fullständig beskrivning.
- **Kategorier utökade (2026-05-02)** — Nya L2/L3-noder: `Bröd & Kakor > Kondis & fika > Kaffebröd` (wienerbröd, donuts, munkar m.m.) och `Dryck > Te & choklad > Te` (chai, vanilla chai, ceylon te m.m.). Nya L3-noder under `Mejeri, ost & ägg > Allergi mejeri`: Laktosfri mjölk, Filmjölk & Yoghurt, Kvarg & Cottage cheese, Matfett, Allergi matlagning.
- **Regelbaserad kategoridetektion utökad (2026-05-02)** — `ruleBasedCategorySuggestion()` täcker nu Te (te, tea, chai, tepas) och Kaffebröd (wienerbröd, donut, munk, croissant, kanelbulle, bakelse, semla m.fl.) utöver befintliga mejeri-regler.
- **AI-guardrail (2026-05-02)** — `AiService.suggestCategory()` remappar nu `low`/`medium`-konfidenspoäng till L1-föräldern istället för att returnera potentiellt fel L2/L3-kategori.
- **Förbättrad produktmatchning (2026-05-02)** — `findWordMatch` normaliserar nu diakritik (ä→a, ö→o, å→a) före jämförelse och tillåter enstaka stark partiell matchning för ord ≥5 tecken (löser t.ex. "vispgrädde" → produkt "grädde").
- **Kategorisuggest för matchade produkter (2026-05-02)** — `matchProducts()` läser nu in `categoryRef` för matchade produkter och sätter `categorySuggestion` direkt. `enrichWithAiCategories()` körs för alla items utan `categorySuggestion`, inte bara ej matchade.
- **Kvittoimport Fas 6b klar (2026-05-01)** — Flutter-granskningsflödet färdigt: per-rad checkbox, redigeringsdialog med destination-väljare (Inventarie/Baslager), merge-förhandsvisning, parallell laddning av inventarie och baslager, snackbar med separat räkning.
- **Kvittoimport Fas 6c klar (2026-05-01)** — Separering av AI-chip och produktsuggestions-chip, produktnamns-normalisering, och validering av AI-kategorier.
- **Microservice-importer integrerad (2026-04-30)** — All import-logik (URL-skrapning, OCR, PDF-parsning, AI-kvittoparsning) delegeras nu till `importer-api` som körs som intern Docker-tjänst. `recipe-api` behåller produktmatchning och AI-kategorisering. Se [migrering-MSI.md](migrering-MSI.md) för detaljer.
- **User-scope för pantry och matplan** — Alla baslager- och matplansdata är nu per användare. Backend och Prisma-schema är migrerade.
- **Robust bildimport** — Bild-URL normaliseras, laddas ner och optimeras i backend. Bilden kopplas till receptet och raderas vid delete. Diagnostikloggning på alla steg.
- **Importflöde** — Quick-import och receipt-import har förbättrats med robust multipart-hantering, timeout, och felhantering.
- **Flutter-parity** — Matplan, inventarie, baslager och receptflöden är nu fullt migrerade till Flutter med user-scope och robust felhantering.
### Kända begränsningar
- Kvittoimport: användare utan egna produkter får inga produktmatchningar — enbart kategorisuggestioner via regler/AI (förväntat beteende, byggs upp allt eftersom).
- `ReceiptAlias` är ännu inte user-scoped (alias är fortfarande globala — se aliasstrategin nedan).
- `receipt_import_tab.dart` (~1 400 rader) och `swipeable_inventory_tile.dart` är ännu inte lokaliserade.
- Vissa adminfunktioner och avancerad AI-integration är planerade men ej migrerade.
---
## Nästa steg
1. Bygg upp UI-flöde för att skapa/spara egna produkter vid kvittobekräftelse (POST /products med `ownerId` från JWT).
2. Inför user-scope för `ReceiptAlias` (lägg till `userId`, unika index, admin-godkänd global fallback).
3. Implementera automatisk alias-inlärning vid manuell korrigering i importflödet.
4. Kvittoimport steg 2: persistenta förpackningsfält i inventarie (packCount, packSizeQuantity, packSizeUnit) + visning/redigering i inventory-UI.
5. Lokalisera `receipt_import_tab.dart` och `swipeable_inventory_tile.dart`.
6. Smoke-test på testdomän och avstämning.
7. Planera och påbörja avancerad AI-integration och EAN-skanning.
8. AI-kategorisering: kandidatbegränsning före AI (narrowing till relevant gren/kandidatlista istället för hela kategoriträdet).
9. AI-kategorisering: egen confidence-score i backend (regel/lexikal signal + AI-signal), inte enbart modellens confidence.
10. AI-kategorisering: logga användarkorrigeringar och bygg feedbackloop för nya regler/synonymer.
11. AI-kategorisering: utvärdera alternativ modell för kategorisering med A/B-test på historiska kvittorader.
## Beslut 2026-05-02 - Aliasstrategi för kvittoimport
- Alias ska vara hybrid: user-scope som standard, globalt alias endast via admin-verifiering.
- Syfte: minimera felträffar mellan användare och samtidigt bygga en kvalitetssäkrad global kunskapsbas.
- Tekniskt nästa steg i backend:
- Lägg till user-scope för `ReceiptAlias` (t.ex. `userId` + relevanta unika index).
- Behåll global alias som fallback för admin-godkända mappningar.
- Tekniskt nästa steg i klient:
- Spara manuell korrigering som alias (default: user-scope).
- Eventuell admin-toggle för att spara alias globalt.
---
## Beslut 2026-04-22 — Pantry och matplan på user-nivå
Pantry och matplan ska inte vara globala. De ska vara användarspecifika i backend.
### Varför
- Nuvarande implementation delar baslager och matplan mellan alla användare.
- Det ger felaktig inköpslogik och otydligt ägarskap i multi-user-läge.
### Krävda backend-ändringar (prio)
1. **PantryItem user-scope i Prisma**
- Lägg till `userId` i `PantryItem` + relation till `User`.
- Ändra unik regel från global `productId` till kombinationen `userId + productId`.
- Lägg index på `userId`.
2. **Pantry API user-scope**
- `GET /api/pantry` ska returnera inloggad användares baslager.
- `POST /api/pantry` ska skapa för inloggad användare.
- `DELETE /api/pantry/:id` ska bara kunna ta bort poster som ägs av inloggad användare.
3. **MealPlan user-scope i Prisma och service**
- Lägg till `userId` i `MealPlanEntry` + relation till `User`.
- Ändra unik regel från global `date` till `userId + date`.
- Filtrera `findByRange`, `upsert` och `removeByDate` per inloggad användare.
4. **Matplanens inventory-compare ska använda user-scope pantry**
- Pantry-uppslag i `inventoryCompare` ska filtreras på samma `userId`.
### Migrering och release-strategi
1. Ta migration för `PantryItem` + `MealPlanEntry`.
2. Backfill: tilldela befintliga poster till vald default-användare (eller rensa och starta om datan i testmiljö).
3. Uppdatera backend-kontrollers till `@CurrentUser()`-mönster.
4. Verifiera med två testanvändare att data är isolerad.
5. Därefter uppdateras Flutter/Next-klienterna mot de nya kontrakten.
---
## Notering 2026-04-20
## Notering 2026-04-21
- Rollskydd i frontend (middleware) och backend (admin på endpoints) är nu heltäckande.
- Rate limiting, CORS-låsning och JWT_SECRET-krav är på plats.
- Konsol-loggar borttagna ur backend.
- Kvar: Kryptering av PII (e-post, namn i databasen), automatisering av seed_all.sql vid fresh install.
Den senaste versionen av `db/seeds/seed_all.sql` är pushad till utvecklingsmiljön, men har **ännu inte körts** på databasen. Nästa gång vi vill uppdatera kategoriträdet måste seed-scriptet köras manuellt:
```bash
docker exec -i recipe-db mariadb -uroot -p"$DB_PASS" recipe_app < db/seeds/seed_all.sql
```
Se även TEKNISK_BESKRIVNING.md för detaljer om seed-processen.
---
## Vägen till produktlansering
- [Säkerhet & Data](produktlansering.md#1-säkerhet--data)
- [DevOps & Stabilitet](produktlansering.md#2-devops--stabilitet)
- [Testning & Kvalitet](produktlansering.md#3-testning--kvalitet)
- [Funktionella förbättringar](produktlansering.md#4-funktionella-förbättringar)
- [Risker & Flaskhalsar](produktlansering.md#5-risker--flaskhalsar)
---
## Prioriterade förbättringar
### 1. ICA-skraparen missar receptbild
**Problem:** Vid import av ICA-recept via URL hämtas inte receptbilden korrekt.
- Felsök parsern i `recipe-document-converter/src/parser.ts` — kontrollera att ICA:s bild-selektor fortfarande matchar sidans HTML-struktur
- ICA kan ha ändrat klassnamn eller lazy-load-mekanism sedan skraparen skrevs
- Verifiera med ett konkret ICA-recept-URL och logga vad `imageUrl` returnerar
### 2. Seed-data ✅
**Klart.** `db/seeds/seed_all.sql` är den enda sanningskällan för kategorier och produkter.
Filen:
- TRUNCATEar `Category` och nollställer `Product.categoryId`
- Bygger hela kategoriträdet (nivå 13) utan dubbletter
- Insertar ~190 produkter (INSERT IGNORE)
- Kopplar produkterna till rätt kategori via JOIN-subqueries
Körs manuellt: `docker exec -i recipe-db mariadb -uroot -p"$DB_PASS" recipe_app < db/seeds/seed_all.sql`
### 2. Refactor: seed_all.sql som del av fresh install
**Mål:** Fresh install ska inte kräva ett manuellt seed-steg.
Nuläge:
- `db/init/001-init.sql` (MariaDB auto-init) skapar bara en bootstrap-check-tabell
- Prisma-migrationen `20260417310000_add_category_tree` seedar kategorier en gång, men seed_all.sql skriver över dem
- `seed_all.sql` körs **inte** automatiskt vid deploy eller fresh install
Alternativ:
- **Alt A:** Flytta seed_all.sql-logiken till `db/init/001-init.sql` — körs automatiskt av MariaDB-containern vid första start
- **Alt B:** Skapa en `prisma/seed.ts` som körs via `prisma db seed` — standard NestJS/Prisma-mönster, kan inkluderas i deploy.sh
- **Alt C:** Lägg till `seed_all.sql` som ett steg i `deploy.sh` (körs alltid, idempotent p.g.a. TRUNCATE+INSERT IGNORE)
Oavsett alternativ bör de gamla kategori-INSERTs i `20260417310000_add_category_tree/migration.sql` tas bort eller kommenteras ur för att undvika förvirring.
### 2. Användarroller och full användarhantering ✅
**Klart.**
Systemet har nu fullständig rollbaserad åtkomstkontroll och ett komplett användarhanteringsgränssnitt inbyggt i profilsidan.
**Rollsystemet:**
- **Prisma-migration** (`20260418100000_add_user_role`) — fältet `role String @default("user")` lades till på `User`-modellen
- **`@Roles('admin')`-dekoratorn** (`auth/decorators/roles.decorator.ts`) — använder `SetMetadata` för att markera endpoints
- **`RolesGuard`** (`auth/roles.guard.ts`) — registrerad globalt som `APP_GUARD`; läser rollmetadata, kastar 403 om rätt roll saknas
- **JWT inkluderar nu `role`** — `jwt.strategy.ts` returnerar `{userId, username, role}`, `auth.service.ts` signerar med `role` i payload
- **Bootstrap-användare** (`users/admin-bootstrap.service.ts`) — `OnApplicationBootstrap` skapar/uppdaterar Nadmin, Padmin, user1 och user2 vid varje uppstart via miljövariabler
- **Skyddade produkt-endpoints** — `@Roles('admin')``merge`, `delete`, `restore`, `reset-all`, `bulk-update`, `backfill-canonical` i `products.controller.ts`
**Backend-endpoints för användarhantering (alla kräver admin-roll):**
- `GET /api/users` — lista alla användare
- `PATCH /api/users/:id/role` — ändra roll
- `POST /api/users` — skapa ny användare (validering: unikt användarnamn och e-post)
- `DELETE /api/users/:id` — ta bort användare (skyddad: kan inte ta bort sig själv)
- `POST /api/users/:id/reset-password` — genererar tillfälligt lösenord, returnerar meddelandetext + lösenord
- `PATCH /api/users/:id/email` — uppdatera e-postadress
**Profilsidan med flikar (`/profil`):**
- `?tab=profil` — Min profil (alla användare)
- `?tab=anvandare` — Användare (enbart admin): skapa, ta bort, rollbyte, e-postbyte, lösenordsåterställning med kopierings-modal
- `?tab=databas` — Databas (enbart admin): produktadmin, nu med undertabbar:
- **Varor:** lista och redigera aktiva produkter
- **Skapa / Slå ihop:** skapa ny produkt, återställ produktdatabas, slå ihop dubbletter
- **Papperskorg:** visa mjukraderade produkter, återställ eller radera permanent
- `/admin/users` omdirigerar till `/profil?tab=anvandare`
- Navigeringslänken "👥 Användare" går direkt till `/profil?tab=anvandare`
### 3. Matplan-vy (frontend-polish) ✅
**Klart.**
Inköpslistan och inventariejämförelsen är sammanslagna till en enhetlig vy med tre statusnivåer:
- ❌ Saknas helt — visar hur mycket som behövs köpas
- ⚠️ Delvis hemma — visar hur mycket mer som behövs + vad som finns
- ✅ Finns hemma — markeras nedtonat, ingen köpindikering
Listan sorteras automatiskt: saknade ingredienser överst, hemma-ingredienser underst. En sammanfattningsrad visar totalt antal per statuskategori.
### 4. Teknisk skuld (underhåll)
**Mål:** Minska komplexitet och risk för buggar.
#### A. CanonicalNameForm och NameForm ✅
Filerna var redan borttagna — inga aktiva imports hittades. Inget att göra.
#### B. Oanvända fält på InventoryItem ✅
Följande 6 fält togs bort via Prisma-migration (`20260418000000_remove_unused_inventory_fields`):
`priority`, `shelfNote`, `isOnSale`, `priceLevel`, `proteinType`, `isLeftover`
- Schema, DTOs (create + update), service och frontend-typen är städade.
- `opened` och `suitableFor` behölls — de används i UI.
#### C. Validering av DTO:er i admin-actions ✅
Redan implementerat — `trim()` + max 100 tecken på alla fält i `actions.ts`. Inget att göra.
#### D. Routing-städning för kvitto och import ✅
`frontend/app/kvitto/page.tsx` och `frontend/app/recipes/import/page.tsx` är borttagna.
`/import` täcker båda use-cases via flikar.
#### E. Seed-data i versionshantering ✅
`data/matvaror_sverige.csv` och `data/seed_products.sql` behålls i git för reproducerbarhet. Inga ändringar i `.gitignore`.
### 5. Avancerad AI-integration
**Mål:** Smarta receptförslag och veckoplanering baserat på inventarie och kampanjdata.
AI-infrastrukturen är nu på plats (`AiService`, `mistral-small-2603`, premium-plan). Kategorisering för produkter och kvittoimport är implementerat. Nästa steg:
- **"Vad ska jag laga idag?"** — Receptförslag baserat på vad som finns i inventariet; kan byggas direkt ovanpå befintliga inventory- och recipe-endpoints
- **Veckoplanering med AI** — Generera ett veckoschemat baserat på inventarie, preferenser och ev. kampanjpriser (kräver extern datakälla)
- Kräver: tydlig API-design, kostnadskontroll och modellval per use-case
---
## AI-loggning och audit
- detta behöver ske innan nästa Ai-integration (receptförslag, veckoplanering) implementeras, för att säkerställa att vi har en robust lösning på plats för att spåra och analysera AI-anrop och resultat
- Utforska och utveckla lösning för loggning och audit av AI-input och output
- Syfte: möjliggöra spårbarhet, felsökning och kvalitetssäkring av AI-funktioner
- Implementera loggning på ett sätt som inte lagrar personuppgifter eller känslig data i klartext
- Utvärdera om loggar ska lagras i separat databas eller fil, och hur de kan användas för analys och förbättring
---
## Enhetsstöd och konvertering
- Se över om vi behöver utöka antalet stödda enhetstyper (t.ex. fler volym- eller portionsenheter)
- Utveckla och förbättra normaliseringen i `recipeservice.normalizeUnit`
- Se över och testa konverteringslogiken för enheter (t.ex. edge cases, felhantering)
- Fundera på om AI kan användas för att tolka, normalisera eller konvertera enheter och mängder smartare
---
## Enhetstester
Jest + ts-jest är uppsatt. Tester finns för:
- `normalize-name.ts` — 10 tester
- `base.parser.ts` (`parseIngredientLine`) — 12 tester
- `recipes.service.ts` (`normalizeUnit`, `convertUnit`) — 17 tester
Kör med `npm test` i `backend/`.
---
## Planerade funktioner
### Säkerhet — kryptering av känslig användardata
**Mål:** Kryptera känsliga fält (t.ex. e-postadresser, personuppgifter) i databasen med en hybridlösning.
Förslag på approach:
- **AES-256-GCM** för symmetrisk kryptering av data i vila (kolumnnivå i MariaDB eller på applikationsnivå i Prisma middleware)
- **Nyckelhantering** via miljövariabel eller extern nyckelhanteringstjänst (t.ex. HashiCorp Vault)
- Kräver genomtänkt migrering av befintlig data och påverkar sökbarhet (krypterade fält kan inte indexeras direkt)
### EAN-skanning — import via streckkod (Open Food Facts)
**Mål:** Låt användaren skanna eller skriva in en EAN-streckkod och få produktinformation (namn, kategori, näringsvärden) ifyllt automatiskt.
Open Food Facts är ett öppet, gratis API utan API-nyckel för grundläggande användning.
**Exempelanrop (EAN 7310960010016):**
```
GET https://world.openfoodfacts.org/api/v0/product/{ean}.json
```
Svaret innehåller:
- `product.product_name` — produktnamn
- `product.categories` — kategoristräng (kommaseparerad)
- `product.nutriments` — näringsvärden (energi, protein, fett, kolhydrater, m.fl.)
- `product.code` — EAN-koden bekräftad
**Förslag på implementation:**
1. **Backend-endpoint**`GET /api/products/ean/:code` — slår upp i Open Food Facts, returnerar normaliserat produktförslag (namn, kategori, näringsvärden) utan att spara
2. **Frontend-import** — nytt fält i produktformuläret (eller separat import-flöde): ange EAN → knapp "Hämta" → formuläret förifylls
3. **Alternativt:** kamerabaserad skanning i mobil webbläsare via `BarcodeDetector` API (eller externt bibliotek som `zxing-js`)
4. **Kategorimappning** — Open Food Facts-kategorier är på engelska/franska; en mappningstabell eller fuzzy-matchning mot systemets kategorier behövs
5. **Näringsdata** — om `product.nutriments` finns kan det sparas direkt i Prisma-modellens näringsvärdesfält (redan schema-klara)
**Begränsningar att ha i åtanke:**
- Produkttäckning varierar — svenska dagligvaror är välrepresenterade men inte allt finns
- Produktnamn är ofta på originalspråk; kan behöva redigeras
- Rate limiting: för hög användning rekommenderar Open Food Facts att kontakta dem
### Byta @Post till @Patch på backfill-canonical
Konsekvenser: ⚠️ Bryta ändring.
Frontend anropar /api/products/backfill-canonical med POST från AdminProductList.
Caddy och Next.js API-routes kan ha hårdkodade metoder.
Kräver synkroniserad ändring i backend + alla anropsställen i frontend, annars slutar funktionen fungera.