Add comprehensive documentation for Flutter frontend migration and backend review
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
- Introduced user guide for Flutter frontend in README.md, detailing user flows and recent improvements. - Created next steps roadmap for Flutter migration in next_steps_flutter.md, outlining current tasks and priorities. - Developed technical description for Flutter frontend in teknisk_beskrivning_flutter.md, covering architecture and security status. - Removed outdated migration documentation for Prisma P3009 and added recovery steps for failed migrations in migrering-MSI.md. - Established a release checklist for product launches in produktlansering.md, ensuring security and stability measures are met. - Formulated a systematic backend review and optimization plan in review_backend.md, focusing on reducing complexity and improving performance.
This commit is contained in:
@@ -0,0 +1,820 @@
|
||||
# Session 2026-05-06: Refaktor och user-scoped AI
|
||||
|
||||
Denna session har genomfört:
|
||||
- **User-scoped AI-fallback:** AI-förslag för ingrediens- och kategorimatchning är nu individuellt aktiverbara per användare (premium).
|
||||
- **Admin-toggles:** Backend och UI har stöd för att admin kan slå på/av AI per användare.
|
||||
- **Premium-scope:** Flutter och backend respekterar premium-flagga och AI-tillgång i alla flöden.
|
||||
- **Rematch och manuell produkt:** Flutter har stöd för ommatchning och manuell produkt vid import.
|
||||
- **Lessons learned:**
|
||||
- Nullable propagation i Prisma och DTO:er kräver noggrannhet.
|
||||
- Fallback-first AI och tydlig separation av analyskontrakt ger robustare flöden.
|
||||
- User-scoped features kräver ownerId/userId-filter i all logik.
|
||||
- Manuella migrationer kan krävas vid DB-problem (se migrering-MSI.md).
|
||||
|
||||
Se även:
|
||||
- [TEKNISK_BESKRIVNING.md](TEKNISK_BESKRIVNING.md) för teknisk genomgång.
|
||||
- [AI-FUNKTIONER.md](_archive/microservice-ai/AI-FUNKTIONER.md) för AI-översikt.
|
||||
|
||||
# Session 2026-05-09: Simplified Matching Logic (Consolidation Phase 3)
|
||||
|
||||
Denna session har genomfört den tredje omarbetningsfasen som konsoliderar receipt-import matching-flödet:
|
||||
|
||||
## Genomförda förbättringar
|
||||
|
||||
### 1. Unified Matcher (`matchAndEnrichReceiptItem`)
|
||||
- **Tidigare:** Matching var splittrad mellan `matchProducts()` (alias + word-match) och `enrichWithAiCategories()` (~850 rader).
|
||||
- **Nu:** En central metod som gör allt i explicit ordning:
|
||||
1. **Alias lookup** — certifierad match från ReceiptAlias
|
||||
2. **Word-match** — fuzzy produktmatchning med scoring
|
||||
3. **Categorization** — regel-baserad → AI (fallback) → guards → hard overrides
|
||||
|
||||
### 2. Improved Context Management (`prepareMatchingContext`)
|
||||
- Alla data (aliases, produkter, unit mappings, categories) hämtas **en gång** per receipt
|
||||
- Parallell loading med `Promise.all()` — högre performance än tidigare sekventiell loading
|
||||
- Context passeras till alla items för effektiv återanvändning
|
||||
|
||||
### 3. Better Decision Logging (`enrichCategoryForItem`)
|
||||
- Structured trace med steg-för-steg-loggning:
|
||||
- ✓ Rule-based hits with path name
|
||||
- ✓ AI suggestions
|
||||
- ⚠️ Guard remaps (contradiction resolution)
|
||||
- ⚠️ Hard overrides (special cases)
|
||||
- ✅ Final decision with confidence level
|
||||
- Debug-träd med alla decision points
|
||||
- Kan aktiveras per receipt via `RECEIPT_TRACE_DECISIONS` env-var
|
||||
|
||||
### 4. Simplified parseReceipt Flow
|
||||
```
|
||||
Before:
|
||||
parseReceipt() → matchProducts() → enrichWithAiCategories()
|
||||
|
||||
After:
|
||||
parseReceipt() → prepareContext() → matchAndEnrichReceiptItem() (per item)
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
**Nya metoder:**
|
||||
- `matchAndEnrichReceiptItem()` — unified matching pipeline per item
|
||||
- `prepareMatchingContext()` — one-time context preparation
|
||||
- `enrichCategoryForItem()` — consolidated categorization logic
|
||||
- `findWordMatchWithScore()` — refactored word matching with explicit score return
|
||||
|
||||
**Datatyper:**
|
||||
- `MatchDecision` interface för strukturerad result (i `dto/match-decision.ts`)
|
||||
|
||||
**Build Status:**
|
||||
- ✅ Full TypeScript compilation successful
|
||||
- ✅ No breaking changes to existing API contracts
|
||||
|
||||
## Cleanup Pending
|
||||
|
||||
De gamla metoderna är fortfarande i koden men kan nu ta bort:
|
||||
- `private async matchProducts()` — deprecated by unified matcher
|
||||
- `private async enrichWithAiCategories()` — deprecated by unified matcher
|
||||
- `private findWordMatch()` — replaced by findWordMatchWithScore()
|
||||
|
||||
**Cleanup tasks:**
|
||||
1. Ta bort `matchProducts()`
|
||||
2. Ta bort `enrichWithAiCategories()`
|
||||
3. Ta bort `findWordMatch()` (gammal version)
|
||||
4. Uppdatera kommentarer/docstrings
|
||||
5. Kör full test suite för regression detection
|
||||
|
||||
Cleanup bör göras i nästa session för att ge tid för monitoring/QA.
|
||||
|
||||
# Plan för omarbetning av receptimport
|
||||
|
||||
# 2026-05-07: Säkerhets- och deployförbättringar
|
||||
|
||||
- **Inventory är nu user-scopad:** Alla inventory-operationer kräver och filtrerar på userId i backend (schema, migration, service, controller, tester).
|
||||
- **IDOR-skydd för inventory:** Det är nu omöjligt för användare att läsa eller ändra andras inventarieposter. Tester verifierar att åtkomst nekas vid försök till IDOR.
|
||||
- **.gitignore och deploy-hygien:** backend/dist och backend/tsconfig.tsbuildinfo ignoreras och är ej längre spårade i git. .env och .env.* ignoreras, men .env.example finns och är uppdaterad.
|
||||
- **CI/CD-härdning:** npm audit och prisma validate körs i pipeline. Alla tester och byggen måste passera.
|
||||
|
||||
## Bakgrund
|
||||
|
||||
Nuvarande importflöde blandar ihop två olika ansvar:
|
||||
|
||||
1. Att importera och spara receptet så troget källan som möjligt.
|
||||
2. Att matcha receptets ingredienser mot interna produkter.
|
||||
|
||||
Det som fungerar bra i dag är:
|
||||
- webskrapning
|
||||
- import med text och bild
|
||||
- presentation av receptbild och text när receptet väl är sparat
|
||||
|
||||
Det som fungerar sämre är:
|
||||
- ingrediensimporten
|
||||
- tidig produktmatchning i importsteget
|
||||
- att receptets innehåll förvrängs eller tappar ingredienser om matchning saknas
|
||||
|
||||
## Målbild
|
||||
|
||||
Importsteget ska bara göra detta:
|
||||
- hämta receptets titel, bild, beskrivning, instruktioner och ingrediensrader
|
||||
- strukturera ingrediensraderna så gott det går
|
||||
- spara receptet även när ingen produktmatchning finns
|
||||
- låta användaren granska receptet utan att behöva välja produkter
|
||||
|
||||
Ett senare analyssteg ska göra detta:
|
||||
- jämföra receptets ingredienser med inventory och pantry
|
||||
- avgöra exakt träff, trolig ersättningsvara eller saknad vara
|
||||
- ge underlag för shoppinglista
|
||||
- ge underlag för AI-förslag och substitutionsförslag
|
||||
|
||||
## Arkitekturprincip
|
||||
|
||||
Receptet ska vara källtroget.
|
||||
Produktmatchning ska vara ett separat lager ovanpå receptet.
|
||||
|
||||
Det innebär att vi behöver separera:
|
||||
- receptets råa ingredienser
|
||||
- användarens lager och skafferi
|
||||
- matchnings- och analyslogik
|
||||
|
||||
## Föreslagen datamodell
|
||||
|
||||
### Nuvarande problem
|
||||
|
||||
I dag kräver `RecipeIngredient` i Prisma:
|
||||
- `productId`
|
||||
- `quantity`
|
||||
- `unit`
|
||||
|
||||
Det gör att en ingrediens inte kan sparas om vi inte redan vet vilken intern produkt den motsvarar.
|
||||
|
||||
### Ny målmodell
|
||||
|
||||
Inför två nivåer för ingredienser:
|
||||
|
||||
1. Receptets egen ingrediensrad
|
||||
2. Matchning/analys mot interna produkter
|
||||
|
||||
### Förslag A: utöka befintlig `RecipeIngredient`
|
||||
|
||||
Det enklaste spåret är att behålla `RecipeIngredient`, men göra den receptcentrisk i stället för produktcentrisk.
|
||||
|
||||
Nya eller ändrade fält:
|
||||
- `rawLine String?`
|
||||
- `rawName String`
|
||||
- `productId Int?` i stället för required
|
||||
- `quantity Decimal?`
|
||||
- `unit String?`
|
||||
- `note String?`
|
||||
- `matchConfidence Float?`
|
||||
- `matchSource String?` (`heuristic`, `ai`, `manual`)
|
||||
- `analysisStatus String?` (`unmatched`, `exact`, `substitutable`, `missing`)
|
||||
|
||||
Behåll:
|
||||
- `alternativeProductIds Json?`
|
||||
- `recipeId`
|
||||
|
||||
### Förslag B: dela upp i två tabeller
|
||||
|
||||
Mer robust på sikt men större ombyggnad:
|
||||
- `RecipeIngredient` blir rå ingrediensrad
|
||||
- `RecipeIngredientMatch` blir separat matchningslager
|
||||
|
||||
Första implementationen bör använda Förslag A för lägre risk.
|
||||
|
||||
## Rekommenderad genomförandeordning
|
||||
|
||||
1. Gör datamodellen tolerant för omatchade ingredienser.
|
||||
2. Ändra backend så att import och sparning fungerar utan produktmatchning.
|
||||
3. Förenkla frontendens importgranskning.
|
||||
4. Lägg till separat analyssteg mot inventory och pantry.
|
||||
5. Lägg till AI-stöd först när grundflödet är stabilt.
|
||||
|
||||
---
|
||||
|
||||
# Fil-för-fil-plan
|
||||
|
||||
## 1. Databas och Prisma
|
||||
|
||||
### Fil: `backend/prisma/schema.prisma`
|
||||
|
||||
### Ändringar
|
||||
- Gör `RecipeIngredient.productId` optional.
|
||||
- Gör `RecipeIngredient.product` optional relation.
|
||||
- Gör `quantity` optional.
|
||||
- Gör `unit` optional.
|
||||
- Lägg till `rawName`.
|
||||
- Lägg till `rawLine`.
|
||||
- Lägg till `matchConfidence`.
|
||||
- Lägg till `matchSource`.
|
||||
- Lägg till `analysisStatus`.
|
||||
|
||||
### Exempel på målmodell
|
||||
|
||||
```prisma
|
||||
model RecipeIngredient {
|
||||
id Int @id @default(autoincrement())
|
||||
recipe Recipe @relation(fields: [recipeId], references: [id])
|
||||
recipeId Int
|
||||
product Product? @relation(fields: [productId], references: [id])
|
||||
productId Int?
|
||||
rawName String
|
||||
rawLine String? @db.Text
|
||||
quantity Decimal? @db.Decimal(10, 2)
|
||||
unit String?
|
||||
note String?
|
||||
alternativeProductIds Json?
|
||||
matchConfidence Float?
|
||||
matchSource String?
|
||||
analysisStatus String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
```
|
||||
|
||||
### Leverabler
|
||||
- Prisma-schema uppdaterat
|
||||
- ny migration skapad
|
||||
- Prisma Client regenererad
|
||||
|
||||
### Risk
|
||||
- All kod som i dag antar att `productId`, `quantity` och `unit` alltid finns måste uppdateras
|
||||
|
||||
---
|
||||
|
||||
## 2. DTO:er för receptskapande
|
||||
|
||||
### Fil: `backend/src/recipes/dto/create-recipe.dto.ts`
|
||||
|
||||
### Nuvarande problem
|
||||
DTO:n kräver att varje ingrediens har:
|
||||
- `productId`
|
||||
- `quantity`
|
||||
- `unit`
|
||||
|
||||
Det blockerar trogen import.
|
||||
|
||||
### Ändringar
|
||||
- Gör `productId` optional.
|
||||
- Gör `quantity` optional.
|
||||
- Gör `unit` optional.
|
||||
- Lägg till `rawName` som required.
|
||||
- Lägg till `rawLine` som optional.
|
||||
- Lägg till `matchConfidence` och `matchSource` som optional om vi vill bevara preliminär heuristik.
|
||||
|
||||
### Mål
|
||||
Backend ska kunna acceptera ett recept där ingrediensen bara är:
|
||||
- `rawName`
|
||||
- eventuellt `rawLine`
|
||||
- eventuellt `quantity`, `unit`, `note`
|
||||
|
||||
### Konsekvens
|
||||
`ArrayMinSize(1)` kan sannolikt vara kvar, men valideringen måste vara råingrediens-baserad i stället för produkt-baserad.
|
||||
|
||||
---
|
||||
|
||||
## 3. DTO för parse-resultat
|
||||
|
||||
### Fil: `backend/src/recipes/dto/parse-markdown.dto.ts`
|
||||
|
||||
### Ändringar
|
||||
Ingen stor strukturändring behövs här om endpointen fortsatt bara tar emot markdown.
|
||||
|
||||
### Eventuell komplettering
|
||||
Om importen senare ska stödja flera källor mer explicit kan ett `sourceType` läggas till:
|
||||
- `markdown`
|
||||
- `web`
|
||||
- `ocr`
|
||||
|
||||
Detta är inte nödvändigt i första omgången.
|
||||
|
||||
---
|
||||
|
||||
## 4. Receptservice: parse ska sluta göra för mycket
|
||||
|
||||
### Fil: `backend/src/recipes/recipes.service.ts`
|
||||
|
||||
### Nuvarande problem
|
||||
`parseMarkdown()` gör i dag flera saker:
|
||||
- anropar importer-api eller lokal parser
|
||||
- hämtar alla produkter
|
||||
- kör heuristisk matchning med normalisering och Levenshtein
|
||||
- returnerar suggestions
|
||||
|
||||
Det gör att importsteget implicit blir ett matchningssteg.
|
||||
|
||||
### Rekommenderad ändring i etapp 1
|
||||
Behåll parse men ändra dess ansvar:
|
||||
- parse ska alltid returnera råingredienser
|
||||
- suggestions får vara optional och sekundära
|
||||
- frontend ska inte vara beroende av suggestions för att kunna spara
|
||||
|
||||
### Konkret refaktorering
|
||||
Dela upp i privata metoder:
|
||||
- `parseRecipeContent(markdown)`
|
||||
- `buildIngredientSuggestions(parsedIngredients)`
|
||||
- `createRecipeFromImport(dto, userId)`
|
||||
|
||||
### Ändringar i `create()`
|
||||
- sluta kräva att alla ingredienser har `productId`
|
||||
- kör `assertProductsActive()` bara för ingredienser som faktiskt har `productId`
|
||||
- spara `rawName` även när produktmatchning finns
|
||||
- spara `rawLine` när det finns
|
||||
|
||||
### Ny metod att lägga till
|
||||
- `analyzeRecipeIngredients(recipeId, userId)`
|
||||
|
||||
Ansvar för `analyzeRecipeIngredients`:
|
||||
- ladda receptets råingredienser
|
||||
- jämför mot inventory
|
||||
- jämför mot pantry
|
||||
- returnera analysstatus per ingrediens
|
||||
- returnera förslag på ersättningsvaror och inköp
|
||||
|
||||
### Ny privat logik i service
|
||||
- `findExactInventoryMatches(...)`
|
||||
- `findPantryMatches(...)`
|
||||
- `findSubstituteCandidates(...)`
|
||||
- `buildShoppingNeeds(...)`
|
||||
|
||||
### Viktig designregel
|
||||
`parseMarkdown()` får gärna returnera suggestions, men `create()` får inte vara beroende av att de finns.
|
||||
|
||||
---
|
||||
|
||||
## 5. Receptcontroller: separera import från analys
|
||||
|
||||
### Fil: `backend/src/recipes/recipes.controller.ts`
|
||||
|
||||
### Ändringar
|
||||
Behåll:
|
||||
- `POST /recipes/parse-markdown`
|
||||
- `POST /recipes`
|
||||
|
||||
Lägg till:
|
||||
- `GET /recipes/:id/analysis`
|
||||
- eventuellt `POST /recipes/:id/rematch`
|
||||
|
||||
### Förslag på ansvar
|
||||
`GET /recipes/:id/analysis`
|
||||
- returnerar en användarspecifik analys av receptet mot inventory och pantry
|
||||
|
||||
`POST /recipes/:id/rematch`
|
||||
- kör ny matchning om användaren har ändrat inventory/pantry eller om heuristiken förbättrats
|
||||
|
||||
### Mål
|
||||
Importen blir en egen sak.
|
||||
Analysen blir en egen endpoint.
|
||||
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 7. Alias-strategi och matchedVia (2026-05-07)
|
||||
|
||||
- Alias-strategin är nu fullt implementerad:
|
||||
- Backend och Flutter stödjer user-alias och globala alias.
|
||||
- matchedVia-badge visas i UI (Alias/Ordmatch/AI).
|
||||
- Användare kan spara egna alias, admins kan spara globala.
|
||||
- Profilsidan har alias-lista för användare, admin-panelen visar produkt-ID och radstruktur.
|
||||
- Backend har 3 nya tester för matchedVia, totalt 66 tester gröna.
|
||||
|
||||
### Fil: `backend/src/recipes/recipes.module.ts`
|
||||
|
||||
### Ändringar
|
||||
Lägg till eventuella nya providers när analyslogik bryts ut:
|
||||
- `RecipeAnalysisService`
|
||||
- eventuellt `RecipeMatchingService`
|
||||
|
||||
### Rekommendation
|
||||
Om analysen växer, bryt ut den från `RecipesService` för att undvika att samma service blir för stor.
|
||||
|
||||
---
|
||||
|
||||
## 7. Ny analysservice
|
||||
|
||||
### Ny fil: `backend/src/recipes/recipe-analysis.service.ts`
|
||||
|
||||
### Ansvar
|
||||
- ta ett sparat recept som indata
|
||||
- analysera varje ingrediens mot inventory och pantry
|
||||
- ta fram:
|
||||
- exakt träff
|
||||
- täcks av pantry
|
||||
- möjlig ersättning
|
||||
- saknas
|
||||
|
||||
### Förslag på output
|
||||
|
||||
```ts
|
||||
{
|
||||
recipeId: 123,
|
||||
ingredients: [
|
||||
{
|
||||
ingredientId: 1,
|
||||
rawName: "gul lök",
|
||||
quantity: 1,
|
||||
unit: "st",
|
||||
status: "exact_match",
|
||||
matchedProductId: 456,
|
||||
matchedProductName: "Gul lök",
|
||||
source: "inventory"
|
||||
}
|
||||
],
|
||||
summary: {
|
||||
exactCount: 3,
|
||||
pantryCount: 2,
|
||||
substituteCount: 1,
|
||||
missingCount: 4
|
||||
},
|
||||
shoppingListCandidates: []
|
||||
}
|
||||
```
|
||||
|
||||
### Nytta
|
||||
Detta blir grunden för:
|
||||
- "kan jag laga detta?"
|
||||
- shoppinglista
|
||||
- AI-förslag
|
||||
|
||||
---
|
||||
|
||||
## 8. Ny matchningstjänst
|
||||
|
||||
### Ny fil: `backend/src/recipes/recipe-matching.service.ts`
|
||||
|
||||
### Ansvar
|
||||
- matcha rå ingrediens mot produktkatalog
|
||||
- inte spara något själv
|
||||
- kunna användas av både import och analys
|
||||
|
||||
### Matchningsnivåer
|
||||
1. Exakt match på normaliserat namn
|
||||
2. Synonym/alias-match
|
||||
3. Kategori- eller taggmatchning
|
||||
4. AI-fallback
|
||||
|
||||
### Varför separat tjänst
|
||||
Det gör att heuristik, aliaslogik och AI-stöd inte behöver ligga inbyggt i `parseMarkdown()`.
|
||||
|
||||
---
|
||||
|
||||
## 9. AI-integration för ingredienstolkning
|
||||
|
||||
### Fil: `backend/src/ai/ai.service.ts`
|
||||
|
||||
### Nuvarande läge
|
||||
AI används redan för kategorisering.
|
||||
|
||||
### Nytt användningsområde
|
||||
Lägg till en separat metod, till exempel:
|
||||
- `suggestIngredientMatches(rawIngredient, candidates)`
|
||||
- `suggestSubstitutions(rawIngredient, availableProducts)`
|
||||
|
||||
### Viktig regel
|
||||
AI ska inte styra om receptet går att spara.
|
||||
AI ska bara förbättra analys och förslag efteråt.
|
||||
|
||||
### Mål
|
||||
AI används där osäkerhet är acceptabel:
|
||||
- substitutionsförslag
|
||||
- kompletterande matchning
|
||||
- shoppingförslag
|
||||
|
||||
---
|
||||
|
||||
## 10. Importskärm i Flutter
|
||||
|
||||
### Fil: `flutter/lib/features/recipes/presentation/create_recipe_screen.dart`
|
||||
|
||||
### Nuvarande problem
|
||||
Skärmen gör i dag detta i review-steget:
|
||||
- checkbox per ingrediens
|
||||
- produktval via suggestions
|
||||
- manuellt tillagda ingredienser med produktdropdown
|
||||
- vid sparning ignoreras ingredienser utan `productId`
|
||||
|
||||
Det gör att användaren i praktiken förväntas produktmatcha importen.
|
||||
|
||||
### Målbild för skärmen
|
||||
Importgranskningen ska fokusera på receptet, inte lagerkoppling.
|
||||
|
||||
### Ändringar
|
||||
Ta bort eller dölja i importläget:
|
||||
- produktdropdownar
|
||||
- krav på produktmatchning
|
||||
- manuell ingrediensskapning via produktlista
|
||||
|
||||
Behåll eller lägg till:
|
||||
- justera titel
|
||||
- justera beskrivning vid behov
|
||||
- justera servings
|
||||
- visa importerade ingrediensrader som text
|
||||
- tillåt enklare rättningar av mängd, enhet, notering och namn
|
||||
- inkludera/uteslut ingrediensrad
|
||||
|
||||
### Ny save-logik
|
||||
Vid `_save()` ska frontend skicka:
|
||||
- `rawName`
|
||||
- `rawLine`
|
||||
- `quantity`
|
||||
- `unit`
|
||||
- `note`
|
||||
- `productId` bara om en automatisk eller manuell matchning redan finns
|
||||
|
||||
### Rekommenderad UI-förändring
|
||||
Dela upp skärmen i två modes:
|
||||
- `import review`
|
||||
- `manual recipe editing`
|
||||
|
||||
Import review ska vara avsevärt enklare än manuell receptredigering.
|
||||
|
||||
---
|
||||
|
||||
## 11. Parsed recipe-domain i Flutter
|
||||
|
||||
### Fil: `flutter/lib/features/recipes/domain/parsed_recipe.dart`
|
||||
|
||||
### Ändringar
|
||||
Lägg till fält som tydligare representerar rå importdata:
|
||||
- `rawLine`
|
||||
- `matchState`
|
||||
- `isParsedSafely`
|
||||
|
||||
### Rekommendation
|
||||
`ParsedIngredient` ska behandlas som importdata, inte som nästan färdig `RecipeIngredient`.
|
||||
|
||||
### Ny riktning
|
||||
`suggestions` ska vara optional hjälpdata i UI, inte en förutsättning för att spara.
|
||||
|
||||
---
|
||||
|
||||
## 12. Flutter-repository för recept
|
||||
|
||||
### Fil: `flutter/lib/features/recipes/data/recipe_repository.dart`
|
||||
|
||||
### Ändringar
|
||||
- uppdatera `createRecipe()` så att request body tillåter råingredienser utan `productId`
|
||||
- lägg till metod:
|
||||
- `fetchRecipeAnalysis(int id)`
|
||||
- lägg till eventuell metod:
|
||||
- `rematchRecipeIngredients(int id)`
|
||||
|
||||
### Mål
|
||||
Frontend ska kunna:
|
||||
- spara troget importerat recept
|
||||
- senare hämta analys mot inventory/pantry
|
||||
|
||||
---
|
||||
|
||||
## 13. API-paths i Flutter
|
||||
|
||||
### Fil: `flutter/lib/core/api/api_paths.dart`
|
||||
|
||||
### Ändringar
|
||||
Lägg till:
|
||||
- `static String analysis(int id) => '/recipes/$id/analysis';`
|
||||
- eventuellt `static String rematch(int id) => '/recipes/$id/rematch';`
|
||||
|
||||
---
|
||||
|
||||
## 14. Receptdomän i Flutter
|
||||
|
||||
### Fil: `flutter/lib/features/recipes/domain/recipe.dart`
|
||||
|
||||
### Nuvarande problem
|
||||
Nuvarande domän ser sannolikt ingredienser som produktkopplade objekt.
|
||||
|
||||
### Ändringar
|
||||
Gör att `RecipeIngredient` klarar:
|
||||
- `productId == null`
|
||||
- `productName == null`
|
||||
- `rawName` finns alltid
|
||||
- `quantity` och `unit` kan vara null eller tomma
|
||||
|
||||
### Viktigt
|
||||
UI för receptdetalj ska kunna visa rå ingredienstext även när ingen produktmatchning finns.
|
||||
|
||||
---
|
||||
|
||||
## 15. Receptdetaljskärm i Flutter
|
||||
|
||||
### Fil: `flutter/lib/features/recipes/presentation/recipe_detail_screen.dart`
|
||||
|
||||
### Ändringar
|
||||
Ingredienslistan ska rendera enligt följande prioritet:
|
||||
1. `rawName` och råa fält
|
||||
2. matchat produktnamn om det finns
|
||||
|
||||
### Exempel
|
||||
Om produktmatchning saknas ska användaren ändå se:
|
||||
- `2 msk olivolja`
|
||||
- `1 gul lök`
|
||||
- `400 g krossade tomater`
|
||||
|
||||
### Lägg till ny sektion
|
||||
- "Har du hemma?"
|
||||
|
||||
Den sektionen ska bygga på `GET /recipes/:id/analysis` i stället för att anta att receptingredienser redan är perfekt produktmatchade.
|
||||
|
||||
---
|
||||
|
||||
## 16. Ny analysdomän i Flutter
|
||||
|
||||
### Ny fil: `flutter/lib/features/recipes/domain/recipe_analysis.dart`
|
||||
|
||||
### Ansvar
|
||||
Representera analysresultat från backend.
|
||||
|
||||
### Förslag på typer
|
||||
- `RecipeAnalysis`
|
||||
- `RecipeIngredientAnalysis`
|
||||
- `RecipeIngredientAvailabilityStatus`
|
||||
|
||||
### Statusvärden
|
||||
- `exactMatch`
|
||||
- `coveredByPantry`
|
||||
- `substitutable`
|
||||
- `missing`
|
||||
|
||||
---
|
||||
|
||||
## 17. Ny provider för analys
|
||||
|
||||
### Fil: `flutter/lib/features/recipes/data/recipe_providers.dart`
|
||||
|
||||
### Ändringar
|
||||
Lägg till provider:
|
||||
- `recipeAnalysisProvider`
|
||||
|
||||
### Ansvar
|
||||
- ladda analys för ett recept
|
||||
- kunna invalidateras när inventory eller pantry ändras
|
||||
|
||||
---
|
||||
|
||||
## 18. Manual recipe editing ska vara separat
|
||||
|
||||
### Fil: `flutter/lib/features/recipes/presentation/recipe_edit_screen.dart`
|
||||
|
||||
### Rekommendation
|
||||
Behåll denna skärm för riktig redigering av receptets struktur.
|
||||
|
||||
### Viktig skillnad mot import
|
||||
- import review = bevara originalet
|
||||
- recipe edit = medveten redigering av receptet
|
||||
|
||||
### Praktisk åtgärd
|
||||
Undvik att återanvända fullständig edit-UI direkt i importflödet.
|
||||
|
||||
---
|
||||
|
||||
## 19. Alias- och produktmatchning
|
||||
|
||||
### Relevanta filer
|
||||
- `backend/src/receipt-import/...`
|
||||
- `backend/src/products/...`
|
||||
- `backend/src/recipes/recipes.service.ts`
|
||||
|
||||
### Åtgärd
|
||||
Flytta all generell ingrediensmatchning till en central tjänst i stället för att ha den dold i `parseMarkdown()`.
|
||||
|
||||
### Mål
|
||||
Samma logik ska kunna användas av:
|
||||
- receptimport
|
||||
- analys mot inventory/pantry
|
||||
- AI-förslag
|
||||
- eventuellt framtida shoppinglista
|
||||
|
||||
---
|
||||
|
||||
## 20. Migration av befintliga data
|
||||
|
||||
### Vad behöver hanteras
|
||||
Befintliga recept har ingredienser som redan är produktkopplade.
|
||||
|
||||
### Strategi
|
||||
- behåll gamla värden
|
||||
- fyll `rawName` med bästa tillgängliga namn från produkt eller befintlig text
|
||||
- sätt `matchSource = 'legacy'` för migrerade rader
|
||||
- sätt `analysisStatus = null` initialt
|
||||
|
||||
### Mål
|
||||
Gamla recept fortsätter fungera utan att behöva byggas om manuellt.
|
||||
|
||||
---
|
||||
|
||||
# Implementationsfaser
|
||||
|
||||
## Fas 1: Gör receptingredienser omatchningsbara
|
||||
|
||||
### Filer
|
||||
- `backend/prisma/schema.prisma`
|
||||
- `backend/src/recipes/dto/create-recipe.dto.ts`
|
||||
- `backend/src/recipes/recipes.service.ts`
|
||||
- `flutter/lib/features/recipes/data/recipe_repository.dart`
|
||||
- `flutter/lib/features/recipes/domain/recipe.dart`
|
||||
|
||||
### Resultat
|
||||
Ett recept kan sparas även när inga ingredienser är produktmatchade.
|
||||
|
||||
---
|
||||
|
||||
## Fas 2: Förenkla importgranskningen
|
||||
|
||||
### Filer
|
||||
- `flutter/lib/features/recipes/presentation/create_recipe_screen.dart`
|
||||
- `flutter/lib/features/recipes/domain/parsed_recipe.dart`
|
||||
|
||||
### Resultat
|
||||
Användaren granskar receptet som recept, inte som lagerobjekt.
|
||||
|
||||
---
|
||||
|
||||
## Fas 3: Lägg till analys mot inventory/pantry
|
||||
|
||||
### Filer
|
||||
- `backend/src/recipes/recipe-analysis.service.ts`
|
||||
- `backend/src/recipes/recipes.controller.ts`
|
||||
- `backend/src/recipes/recipes.module.ts`
|
||||
- `flutter/lib/core/api/api_paths.dart`
|
||||
- `flutter/lib/features/recipes/domain/recipe_analysis.dart`
|
||||
- `flutter/lib/features/recipes/data/recipe_repository.dart`
|
||||
- `flutter/lib/features/recipes/data/recipe_providers.dart`
|
||||
- `flutter/lib/features/recipes/presentation/recipe_detail_screen.dart`
|
||||
|
||||
### Resultat
|
||||
Appen kan svara på:
|
||||
- vad har jag exakt
|
||||
- vad täcks av pantry
|
||||
- vad kan ersättas
|
||||
- vad saknas
|
||||
|
||||
---
|
||||
|
||||
## Fas 4: Förbättra matchning och AI
|
||||
|
||||
### Filer
|
||||
- `backend/src/recipes/recipe-matching.service.ts`
|
||||
- `backend/src/ai/ai.service.ts`
|
||||
- eventuellt fler UI-filer för ersättningsförslag och shoppinglista
|
||||
|
||||
### Resultat
|
||||
AI blir en förbättrare, inte ett krav för importen.
|
||||
|
||||
---
|
||||
|
||||
# Beslut som bör tas innan implementation
|
||||
|
||||
## 1. Ska `RecipeIngredient` byggas om eller delas upp?
|
||||
|
||||
Rekommendation:
|
||||
- börja med att bygga om `RecipeIngredient`
|
||||
- dela upp i fler tabeller först om behovet blir tydligt senare
|
||||
|
||||
## 2. Ska suggestions visas under import?
|
||||
|
||||
Rekommendation:
|
||||
- ja, men bara som passiv hjälp
|
||||
- inte som något användaren måste välja för att kunna spara
|
||||
|
||||
## 3. Ska användaren kunna redigera ingrediensrader under import?
|
||||
|
||||
Rekommendation:
|
||||
- ja, men bara lätt redigering
|
||||
- ingen tung produktkoppling i detta steg
|
||||
|
||||
## 4. När ska AI användas?
|
||||
|
||||
Rekommendation:
|
||||
- efter att receptet är sparat
|
||||
- för matchning, substitutioner och shoppingstöd
|
||||
- inte för att avgöra om receptet får sparas
|
||||
|
||||
---
|
||||
|
||||
# Definition av klart
|
||||
|
||||
Denna omarbetning är klar när följande gäller:
|
||||
|
||||
1. Ett importerat recept kan sparas utan att någon ingrediens är kopplad till en intern produkt.
|
||||
2. Receptets ingredienslista visas troget även utan produktmatchning.
|
||||
3. Importskärmen kräver inte produktval.
|
||||
4. Receptdetaljen kan analysera receptet mot inventory och pantry i ett separat steg.
|
||||
5. Analysen kan särskilja exakt träff, pantry-träff, ersättningsbar och saknad ingrediens.
|
||||
6. AI används bara som hjälplager ovanpå detta.
|
||||
|
||||
---
|
||||
|
||||
# Rekommenderat första arbetsblock
|
||||
|
||||
Om implementationen ska påbörjas direkt är detta bästa första block:
|
||||
|
||||
1. Uppdatera `schema.prisma` för råingrediensstöd.
|
||||
2. Skapa migration.
|
||||
3. Uppdatera `create-recipe.dto.ts`.
|
||||
4. Uppdatera `recipes.service.ts#create()` så att recept kan sparas utan `productId`.
|
||||
5. Uppdatera `create_recipe_screen.dart` så att `_save()` skickar råingredienser i stället för att kasta bort omatchade rader.
|
||||
|
||||
Det blocket ger störst effekt med lägst risk, eftersom det löser kärnproblemet: att importen inte längre förstör receptets innehåll.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
@@ -0,0 +1,240 @@
|
||||
# Nyheter och förbättringar (2026-05-10)
|
||||
|
||||
- **Admin-inventarie:** Full CRUD, merge, filter, sortering, preview och säkerhet för admin i inventarietabellen. Endast admin kan se och hantera alla användares inventarieposter via nya endpoints och adminpanel i Flutter.
|
||||
- **User-scope och IDOR-skydd:** Inventory och produkter är nu strikt user-scopade. Alla operationer kräver och filtrerar på userId. Tester verifierar att åtkomst nekas vid försök till IDOR.
|
||||
- **Säkerhetshärdning:** DTO-validering, guard-ordning, logging, throttling, merge abuse-skydd, och rollbaserad access är implementerat och testat.
|
||||
- **Optimeringar:** DRY i service-lager, striktare query parsing, preview-cache, API-cleanup, och kodduplication eliminerad.
|
||||
- **Testtäckning:** Utökade enhets-, integrations- och säkerhetstester för alla kritiska flöden.
|
||||
|
||||
# Sessionlogg: Receipt Import Cleanup & Optimization
|
||||
|
||||
Datum: 2026-05-09
|
||||
|
||||
## Mål under sessionen
|
||||
- Rensa bort legacy/deprecated kod i receipt-import.
|
||||
- Förenkla och optimera kvarvarande kod på ett säkert sätt.
|
||||
- Säkerställa att kategori-förslag visas för okända varor i import-UI.
|
||||
|
||||
## Genomförda förändringar
|
||||
|
||||
### 1) Legacy/deprecated kod borttagen (backend)
|
||||
Fil: `backend/src/receipt-import/receipt-import.service.ts`
|
||||
- Borttaget: `inferPackageDebugFromRawName()`
|
||||
- Borttaget: `cachedCategories` + `loadCategories()` + constructor-anrop
|
||||
- Borttaget: `matchProducts()`
|
||||
- Borttaget: `findWordMatch()` (gammal variant)
|
||||
- Borttaget: `enrichWithAiCategories()`
|
||||
|
||||
Fil: `backend/src/receipt-import/receipt-import.controller.ts`
|
||||
- Borttaget endpoint: `POST /receipt-import/refresh-categories` (obsolet efter borttagen cache-metod)
|
||||
|
||||
### 2) Tester uppdaterade till unified matcher
|
||||
Fil: `backend/src/receipt-import/receipt-import.service.spec.ts`
|
||||
- Tester migrerade från anrop av borttagna `matchProducts()` till `matchAndEnrichReceiptItem()`.
|
||||
- Tester gröna efter uppdatering.
|
||||
|
||||
### 3) Säkra förenklingar/optimeringar (backend)
|
||||
Fil: `backend/src/receipt-import/receipt-import.service.ts`
|
||||
- Infört tydliga typer för matchningskontext (`MatchingContext`) och debug-objekt.
|
||||
- Extraherat helpers för:
|
||||
- signaltextbyggande
|
||||
- enhetsmappning
|
||||
- Reducerad duplicering i kategoriseringsflöde.
|
||||
- In-memory index i matchningskontext för snabbare uppslag:
|
||||
- `aliasByReceiptName`
|
||||
- `unitMappingByKey`
|
||||
- Bakåtkompatibilitet behållen via fallback när index-fält saknas.
|
||||
|
||||
### 4) UI-fix: kategori-förslag visades inte för okända varor
|
||||
Fil: `flutter/lib/features/import/presentation/receipt_import_tab.dart`
|
||||
- Fixat så kategori-förslag visas även om rad saknar matchad produkt.
|
||||
- Edit-state fylls nu även för rader med endast kategori-förslag.
|
||||
- Label i UI visar `Kategoriförslag` när produkt saknas.
|
||||
|
||||
### 5) Diagnostik tillagd (backend)
|
||||
Fil: `backend/src/receipt-import/receipt-import.service.ts`
|
||||
- Varningslogg om kategorier inte kunde laddas eller om listan blev tom.
|
||||
- Syfte: snabbare felsökning när kategori-förslag uteblir.
|
||||
|
||||
## Verifiering under sessionen
|
||||
- Backend build: OK (`npm run build`)
|
||||
- Backend tests: OK (66/66)
|
||||
- Flutter analyze (berörda filer): OK
|
||||
|
||||
Notering: terminalhistorik innehöll äldre felutskrifter, men senaste verifieringarna var gröna.
|
||||
|
||||
## Kvar att göra nästa gång
|
||||
1. Deploya senaste backend + flutter till servern.
|
||||
2. Re-testa receipt import med PDF i produktion.
|
||||
3. Bekräfta att rader utan produkt nu visar `Kategoriförslag` direkt i listan.
|
||||
4. Vid fortsatt problem: kontrollera nya varningsloggar från `prepareMatchingContext` i backend-loggar.
|
||||
|
||||
## Snabb fortsättning (checklista)
|
||||
- [ ] Deploy backend
|
||||
- [ ] Deploy flutter
|
||||
- [ ] Import-test med samma PDF
|
||||
- [ ] Verifiera kategori-förslag för okända varor
|
||||
- [ ] Dela loggutdrag om något saknas
|
||||
|
||||
---
|
||||
|
||||
# Sessionlogg: Produkthantering & UI-optimeringar (samma dag, senare)
|
||||
|
||||
## Mål under denna del
|
||||
- Fixa scroll-issue i kvittoimport-gränssnittet (7 rader men bara 5 synliga).
|
||||
- Implementera product rename/merge för både admins och regular users.
|
||||
- Eliminera kodduplicering i backend-service.
|
||||
- Optimera admin-panel och deploy-process.
|
||||
|
||||
## Genomförda förändringar
|
||||
|
||||
### 1) Scroll-issue fixat i receipt import UI
|
||||
Fil: `flutter/lib/features/import/presentation/receipt_import_tab.dart`
|
||||
- **Problem:** `SizedBox(height: 620px, child: ListView.builder(...))` inuti `SingleChildScrollView` skapade konfligerade scroll-handlers.
|
||||
- **Lösning:** Tog bort `SizedBox`-begränsningen, använd `shrinkWrap: true` och `physics: NeverScrollableScrollPhysics()` på ListView så parent `SingleChildScrollView` hanterar all scrolling.
|
||||
- **Resultat:** ✅ Alla 7 rader nu synliga.
|
||||
|
||||
### 2) Product rename & merge för admin (backend)
|
||||
Fil: `backend/src/products/products.controller.ts`
|
||||
- Två nya endpoints:
|
||||
- `PATCH /products/:id/canonical-name` — admin kan byta namn på vilken produkt som helst
|
||||
- `POST /products/merge` — admin kan slå ihop två produkter
|
||||
- Decorator: `@Roles('admin')`
|
||||
|
||||
### 3) Private product endpoints för vanliga users (backend)
|
||||
Fil: `backend/src/products/products.controller.ts`
|
||||
- Två nya parallella endpoints för user-owned private products:
|
||||
- `PATCH /products/private/:id/canonical-name` — user kan byta namn på egen privat produkt
|
||||
- `POST /products/private/merge` — user kan slå ihop egna privata produkter
|
||||
- JWT extraction: `const userId = req.user.id` (ingen `@Roles`-behov, user kan bara redigera sin egna data)
|
||||
- Security: `ForbiddenException` om produkt inte är privat eller inte ägs av user
|
||||
|
||||
### 4) Backend service refaktorering — kodduplicering eliminerad
|
||||
Fil: `backend/src/products/products.service.ts`
|
||||
- Två nya **private helper-metoder:**
|
||||
- `_updateCanonicalNameCore(id, canonicalName)` — shared logik för trim + Prisma update
|
||||
- `_mergeCore(sourceId, targetId)` — shared logik för transaction, inventory transfer, soft-delete
|
||||
- Alla fyra public metoder (`updateCanonicalName`, `updateCanonicalNamePrivate`, `merge`, `mergePrivate`) använder nu dessa helpers
|
||||
- **Exception fix:** Bytte `throw new Error(...)` till `throw new ForbiddenException(...)` för authorization-fel (korrekt HTTP 403)
|
||||
- **Resultat:** ~80 rader kodduplicering eliminerad, bättre underhållbarhet
|
||||
|
||||
### 5) API-path konstanter för Flutter (frontend)
|
||||
Fil: `flutter/lib/core/api/api_paths.dart`
|
||||
- Lade till två nya constants i `ProductApiPaths`:
|
||||
- `static const mergePrivate = '/products/private/merge'`
|
||||
- `static String canonicalNamePrivate(int id) => '/products/private/$id/canonical-name'`
|
||||
|
||||
### 6) Admin repository uppdaterad (frontend)
|
||||
Fil: `flutter/lib/features/admin/data/admin_repository.dart`
|
||||
- Två nya metoder:
|
||||
- `updateCanonicalNamePrivate(int productId, String canonicalName)` — user rename
|
||||
- `mergeProductsPrivate({required sourceId, required targetId})` — user merge
|
||||
- Komment: "Admin kan uppdatera vilken produkt som helst; users kan bara uppdatera sina egna privata produkter"
|
||||
|
||||
### 7) Admin panel optimeringar (frontend)
|
||||
Fil: `flutter/lib/features/admin/presentation/admin_products_panel.dart`
|
||||
- **Caching av kategorierna:** `_cachedCategoryOptions` beräknas en gång istället för varje build
|
||||
- **Enklare `_nameForId()`:** Bytte från `where().toList()` till en enkel for-loop med early return
|
||||
- **Parallell restore:** `_restoreSelected()` använder `Future.wait()` istället för seriebaserade await
|
||||
- **Expression switch:** `_sortLabel()` förkortat från 12-radigt switch till en enda rad med expression switch
|
||||
|
||||
### 8) Deploy-script förbättring
|
||||
Fil: `deploy.sh`
|
||||
- Nya flaggor för selektiv build:
|
||||
- `--backend` — bygga bara backend
|
||||
- `--flutter` — bygga bara Flutter
|
||||
- `--importer` — bygga bara microservice-importer
|
||||
- `--pull-always` — tvinga Docker att hämta senaste base image (för prod-säkerhet)
|
||||
- Default: bygger alla tre, använder `--pull=false` för snabbhet (ej prod)
|
||||
- `--seed` flag för opt-in databaskällning
|
||||
- Help: `./deploy.sh --help` visar användning
|
||||
|
||||
## Verifiering
|
||||
- ✅ Backend build: OK (`npm run build`)
|
||||
- ✅ Backend tests: OK
|
||||
- ✅ Flutter analyze: OK (alla berörda filer)
|
||||
- ✅ TypeScript-fel: Inga
|
||||
- ✅ Git diff: Alla 4 filer granskat och godkänt
|
||||
|
||||
## Öppna uppgifter (nästa steg)
|
||||
1. Deploy backend + Flutter med `./deploy.sh --backend --flutter`
|
||||
2. Testa i produktion:
|
||||
- Verifiera 7 rader nu synliga i receipt import
|
||||
- Verifiera admin kan byta namn på produkter
|
||||
- Verifiera admin kan slå ihop produkter
|
||||
- Verifiera users kan redigera sina egna privata produkter
|
||||
3. UI för users: Om private rename/merge ska exponeras i användar-app (backend redan klart, saknas bara UI)
|
||||
4. Unit/integration tests för private endpoints
|
||||
|
||||
---
|
||||
|
||||
# Sessionlogg: Aliasstrategi i kvittoimport (samma dag, senare)
|
||||
|
||||
## Mål under denna del
|
||||
- Göra aliasstrategin konsekvent med user-scope som standard och global fallback via admin.
|
||||
- Sluta lära alias automatiskt vid manuell korrigering och kräva explicit val i UI.
|
||||
- Härda backend mot brusiga eller ogiltiga alias.
|
||||
|
||||
## Genomförda förändringar
|
||||
|
||||
### 1) Gemensam aliasnormalisering och guardrails (backend)
|
||||
Filer:
|
||||
- `backend/src/common/utils/receipt-alias.ts`
|
||||
- `backend/src/receipt-alias/receipt-alias.service.ts`
|
||||
- `backend/src/receipt-import/receipt-import.service.ts`
|
||||
|
||||
- Infört gemensam utility för aliasnormalisering (`trim`, lowercase, kollapsad whitespace).
|
||||
- Infört validering som blockerar tomma alias och brusiga alias som `rabatt`, `summa`, `pant`, `att betala`, `totalt`, m.fl.
|
||||
- Receipt import och alias-API använder nu samma regler för både lookup och sparande.
|
||||
|
||||
### 2) Receipt import lär inte längre alias automatiskt (Flutter)
|
||||
Filer:
|
||||
- `flutter/lib/features/import/data/receipt_import_session.dart`
|
||||
- `flutter/lib/features/import/presentation/edit_dialog.dart`
|
||||
- `flutter/lib/features/import/presentation/receipt_import_tab.dart`
|
||||
|
||||
- Infört explicit `learnAlias`-val i edit-dialogen.
|
||||
- Alias sparas nu bara om användaren aktivt markerar att kvittonamnet ska läras in.
|
||||
- Valet persisteras i receipt import-sessionen så att tabbyte inte tappar användarens val.
|
||||
- Om raden redan matchades via alias visas förklarande text i stället för ny aliasinlärning.
|
||||
|
||||
### 3) Aliasöversikter visar scope tydligare (Flutter)
|
||||
Filer:
|
||||
- `flutter/lib/features/admin/domain/receipt_alias.dart`
|
||||
- `flutter/lib/features/profile/presentation/user_aliases_screen.dart`
|
||||
- `flutter/lib/features/admin/presentation/admin_aliases_panel.dart`
|
||||
|
||||
- Aliasmodellen utökad med `ownerId` och `isGlobal`.
|
||||
- User alias-skärmen visar nu skillnad mellan `Privat alias` och `Global fallback`.
|
||||
- Delete-knapp visas bara för privata alias i användarvyn, så UI:t matchar backend-behörigheten.
|
||||
- Adminpanelen visar scope även för aliasposter.
|
||||
|
||||
### 4) Tester för aliasflödet
|
||||
Filer:
|
||||
- `backend/src/receipt-import/receipt-import.service.spec.ts`
|
||||
- `backend/src/receipt-alias/receipt-alias.service.spec.ts`
|
||||
|
||||
- Tester tillagda för normalisering av whitespace vid alias-lookup.
|
||||
- Tester tillagda för alias-upsert med normalisering.
|
||||
- Tester tillagda för blockering av brusalias.
|
||||
- Tester tillagda för behörighetsregler kring globala alias och borttagning.
|
||||
|
||||
## Verifiering
|
||||
- ✅ Backend tests: 31/31 gröna för berörda aliasspecar
|
||||
- ✅ Flutter analyze: OK för alla berörda alias/import-filer
|
||||
|
||||
## Kvar att göra
|
||||
1. Manuell test i appen: receipt import med explicit alias-inlärning.
|
||||
2. Produktionstest: verifiera att privata alias och global fallback beter sig rätt mot riktiga kvitton.
|
||||
3. Bedöm om aliasöversikterna behöver mer avancerad filtrering eller redigering senare.
|
||||
|
||||
## Snabb checklista för nästa session
|
||||
- [ ] Deploy backend + Flutter
|
||||
- [ ] Testa scroll-fix i prod
|
||||
- [ ] Testa admin rename/merge
|
||||
- [ ] Testa private endpoints (API-test eller manual)
|
||||
- [ ] Implementera user-UI för private rename/merge (valfritt)
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
@@ -0,0 +1,414 @@
|
||||
# 🔒 Säkerhetshärdningsplan för Recipe-App (Flutter + NestJS + MariaDB)
|
||||
# Nyheter och förbättringar (2026-05-10)
|
||||
|
||||
- **Admin-inventarie:** Endast admin har tillgång till CRUD, merge, filter, sortering och preview för alla användares inventarieposter. Endpoints och UI är skyddade med @Roles('admin') och testade.
|
||||
- **User-scope och IDOR-skydd:** Inventory och produkter är strikt user-scopade. Alla operationer kräver och filtrerar på userId. Tester verifierar att åtkomst nekas vid försök till IDOR.
|
||||
- **Säkerhetshärdning:** DTO-validering, guard-ordning, logging, throttling, merge abuse-skydd, och rollbaserad access är implementerat och testat.
|
||||
- **Optimeringar:** DRY i service-lager, striktare query parsing, preview-cache, API-cleanup, och kodduplication eliminerad.
|
||||
- **Testtäckning:** Utökade enhets-, integrations- och säkerhetstester för alla kritiska flöden.
|
||||
|
||||
|
||||
**Reviderad:** 2026-05-07 — baserad på faktisk kodgranskning av repo.
|
||||
|
||||
**Mål:** Täppa till IDOR, Full Table Dump, och andra kritiska säkerhetshål i **backend (NestJS/Prisma/MariaDB)**, **Flutter-frontend**, och **infrastruktur (Docker/Gitea/Ubuntu)**.
|
||||
|
||||
**Prioritet:** CRITICAL → HIGH → MEDIUM
|
||||
|
||||
---
|
||||
|
||||
## ✅ **Redan implementerat — kräver ingen åtgärd**
|
||||
|
||||
Följande är bekräftat implementerat i koden och behöver inte åtgärdas:
|
||||
|
||||
| Åtgärd | Var |
|
||||
|---|---|
|
||||
| Helmet med HSTS, X-Frame-Options, CORP, COOP, Referrer-Policy | `backend/src/main.ts` |
|
||||
| CORS begränsat till `ALLOWED_ORIGIN` (env-var) | `backend/src/main.ts` |
|
||||
| Global `JwtAuthGuard` (alla endpoints kräver JWT om inte `@Public()`) | `backend/src/app.module.ts` |
|
||||
| Global `RolesGuard` med `@Roles('admin')` decorator | `backend/src/app.module.ts` |
|
||||
| Global `ThrottlerGuard` (120 anrop/min), 10/min på login/register | `backend/src/app.module.ts`, `auth.controller.ts` |
|
||||
| `ValidationPipe` med `whitelist` + `forbidNonWhitelisted` (förhindrar mass assignment) | `backend/src/main.ts` |
|
||||
| bcrypt salt 12 för lösenordshashning | `backend/src/auth/auth.service.ts` |
|
||||
| IDOR-skydd: `recipes`, `pantry`, `meal-plan`, `receipt-alias` filtrerar på `userId` | Respektive controllers |
|
||||
| Produkter är user-scoped (`ownerId` på `Product`) | `backend/prisma/schema.prisma` |
|
||||
| Hemligheter som env-vars i compose, inga hårdkodade värden | `compose.yml` |
|
||||
| `.env.*` i `.gitignore` | `.gitignore` |
|
||||
| Token-lagringsabstraktion i Flutter (`ITokenStorage`) | `flutter/lib/core/platform/token_storage.dart` |
|
||||
| Premium/admin-guard (`PremiumOrAdminGuard`) | `backend/src/auth/premium-or-admin.guard.ts` |
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 📌 **0. Förberedelser**
|
||||
|
||||
### 0.1. Miljö och verktyg
|
||||
|
||||
- **Skapa en `security-audit`-gren:**
|
||||
```bash
|
||||
git checkout -b security-audit-$(date +%Y%m%d)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 🚨 **1. IDOR + Full Table Dump i Inventory — CRITICAL**
|
||||
|
||||
**Problem:** `InventoryItem` saknar `userId`-fält i Prisma-schemat och `inventory.controller.ts` använder inte `@CurrentUser()` alls. Alla autentiserade användare kan läsa, modifiera och radera varandras inventarieposter via `GET/POST/PATCH/DELETE /api/inventory`.
|
||||
|
||||
**Nuläge i koden:**
|
||||
- `InventoryItem`-modellen i `schema.prisma` saknar `userId`-kolumn
|
||||
- `inventory.controller.ts` har noll anrop till `@CurrentUser()`
|
||||
- `inventory.service.ts` metoder `findAll()`, `findExpiring()`, `update()`, `remove()` tar inte in `userId`
|
||||
|
||||
**Lösning — 3 steg:**
|
||||
|
||||
### Steg 1: Lägg till `userId` i Prisma-schemat
|
||||
|
||||
```prisma
|
||||
// backend/prisma/schema.prisma
|
||||
model InventoryItem {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int // NY KOLUMN
|
||||
productId Int
|
||||
// ...övriga fält oförändrade...
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||||
consumptions InventoryConsumption[]
|
||||
|
||||
@@index([productId])
|
||||
@@index([userId]) // NY INDEX
|
||||
}
|
||||
|
||||
// Lägg till i User-modellen:
|
||||
model User {
|
||||
// ...
|
||||
inventoryItems InventoryItem[] // NY RELATION
|
||||
}
|
||||
```
|
||||
|
||||
Skapa migrerings-SQL i `backend/prisma/migrations/{timestamp}_add_userid_to_inventory/migration.sql`:
|
||||
```sql
|
||||
ALTER TABLE `InventoryItem` ADD COLUMN `userId` INT NOT NULL DEFAULT 1;
|
||||
-- Backfill: sätt userId baserat på produktens ägare
|
||||
UPDATE `InventoryItem` ii
|
||||
JOIN `Product` p ON ii.productId = p.id
|
||||
SET ii.userId = p.ownerId;
|
||||
ALTER TABLE `InventoryItem` ADD CONSTRAINT `fk_inventory_user`
|
||||
FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE;
|
||||
CREATE INDEX `InventoryItem_userId_idx` ON `InventoryItem`(`userId`);
|
||||
```
|
||||
> **OBS:** Granska backfill-SQL mot din data innan körning — om produkter saknar ägare kan DEFAULT 1 ge fel.
|
||||
|
||||
### Steg 2: Uppdatera service-metoderna
|
||||
|
||||
```typescript
|
||||
// backend/src/inventory/inventory.service.ts
|
||||
|
||||
// findAll — lägg till userId-parameter och where-klausul
|
||||
async findAll(userId: number, query?: InventoryQuery) {
|
||||
const where: Prisma.InventoryItemWhereInput = { userId };
|
||||
// ...resten oförändrat...
|
||||
}
|
||||
|
||||
// findExpiring — filtrera på userId
|
||||
async findExpiring(userId: number) {
|
||||
return this.prisma.inventoryItem.findMany({
|
||||
where: { userId, bestBeforeDate: { not: null, gte: new Date() } },
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
// create — spara userId
|
||||
async create(userId: number, data: CreateInventoryDto) {
|
||||
return this.prisma.inventoryItem.create({
|
||||
data: { ...data, userId, quantity: new Prisma.Decimal(data.quantity) },
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
// update — verifiera ägarskap innan uppdatering
|
||||
async update(id: number, userId: number, data: UpdateInventoryDto) {
|
||||
const existing = await this.findInventoryItemByIdOrThrow(id);
|
||||
if (existing.userId !== userId) throw new ForbiddenException('Åtkomst nekad');
|
||||
// ...resten oförändrat...
|
||||
}
|
||||
|
||||
// remove — verifiera ägarskap innan borttagning
|
||||
async remove(id: number, userId: number) {
|
||||
const existing = await this.findInventoryItemByIdOrThrow(id);
|
||||
if (existing.userId !== userId) throw new ForbiddenException('Åtkomst nekad');
|
||||
return this.prisma.inventoryItem.delete({ where: { id } });
|
||||
}
|
||||
|
||||
// consume — verifiera ägarskap
|
||||
async consume(id: number, userId: number, body: ConsumeInventoryDto) {
|
||||
const existing = await this.findInventoryItemByIdOrThrow(id);
|
||||
if (existing.userId !== userId) throw new ForbiddenException('Åtkomst nekad');
|
||||
// ...resten oförändrat...
|
||||
}
|
||||
```
|
||||
|
||||
### Steg 3: Uppdatera controller
|
||||
|
||||
```typescript
|
||||
// backend/src/inventory/inventory.controller.ts
|
||||
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||
import { ForbiddenException } from '@nestjs/common';
|
||||
|
||||
@Controller('inventory')
|
||||
export class InventoryController {
|
||||
@Get()
|
||||
findAll(
|
||||
@CurrentUser() user: { userId: number },
|
||||
@Query('location') location?: string,
|
||||
@Query('sort') sort?: string,
|
||||
) {
|
||||
return this.inventoryService.findAll(user.userId, { location, sort });
|
||||
}
|
||||
|
||||
@Get('expiring')
|
||||
findExpiring(@CurrentUser() user: { userId: number }) {
|
||||
return this.inventoryService.findExpiring(user.userId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
create(
|
||||
@CurrentUser() user: { userId: number },
|
||||
@Body() body: CreateInventoryDto,
|
||||
) {
|
||||
return this.inventoryService.create(user.userId, body);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(
|
||||
@CurrentUser() user: { userId: number },
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() body: UpdateInventoryDto,
|
||||
) {
|
||||
return this.inventoryService.update(id, user.userId, body);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(
|
||||
@CurrentUser() user: { userId: number },
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
) {
|
||||
return this.inventoryService.remove(id, user.userId);
|
||||
}
|
||||
|
||||
@Post(':id/consume')
|
||||
consume(
|
||||
@CurrentUser() user: { userId: number },
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() body: ConsumeInventoryDto,
|
||||
) {
|
||||
return this.inventoryService.consume(id, user.userId, body);
|
||||
}
|
||||
|
||||
@Get(':id/consumption-history')
|
||||
findConsumptionHistory(
|
||||
@CurrentUser() user: { userId: number },
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
) {
|
||||
// Verifiera ägarskap på item innan historik returneras
|
||||
return this.inventoryService.findConsumptionHistory(id, user.userId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Steg 4: Testa
|
||||
|
||||
```typescript
|
||||
// backend/src/inventory/inventory.service.spec.ts
|
||||
describe('IDOR-skydd inventory', () => {
|
||||
it('nekar åtkomst till annan användares post vid update', async () => {
|
||||
prismaMock.inventoryItem.findUnique.mockResolvedValue({ id: 1, userId: 2 } as any);
|
||||
await expect(service.update(1, 1, {})).rejects.toThrow(ForbiddenException);
|
||||
});
|
||||
it('nekar åtkomst till annan användares post vid remove', async () => {
|
||||
prismaMock.inventoryItem.findUnique.mockResolvedValue({ id: 1, userId: 2 } as any);
|
||||
await expect(service.remove(1, 1)).rejects.toThrow(ForbiddenException);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## �️ **2. dist/ saknas i .gitignore — HIGH**
|
||||
|
||||
**Problem:** `backend/dist/` och `backend/tsconfig.tsbuildinfo` är committade i repot och orsakar merge-konflikter vid deploy (bekräftat 2026-05-07).
|
||||
|
||||
**Lösning:**
|
||||
|
||||
Lägg till i `.gitignore` (rotnivå):
|
||||
```gitignore
|
||||
# Kompilerat backend-output — byggs i Docker, ska ej spåras
|
||||
backend/dist/
|
||||
backend/tsconfig.tsbuildinfo
|
||||
```
|
||||
|
||||
Rensa sedan från git-historiken (enbart lokal tracking):
|
||||
```bash
|
||||
git rm -r --cached backend/dist/ backend/tsconfig.tsbuildinfo
|
||||
git commit -m "chore: untrack compiled backend dist files"
|
||||
git push
|
||||
```
|
||||
|
||||
På servern, kör sedan:
|
||||
```bash
|
||||
git checkout -- backend/dist/ backend/tsconfig.tsbuildinfo 2>/dev/null; git pull
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 🔑 **3. .gitignore täcker inte bare `.env` — MEDIUM**
|
||||
|
||||
**Problem:** `.gitignore` har `.env.*` (täcker `.env.local`, `.env.production` etc.) men INTE ett eventuellt `.env` utan suffix.
|
||||
|
||||
**Lösning:** Lägg till i `.gitignore`:
|
||||
```gitignore
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
```
|
||||
|
||||
**Skapa `.env.example`** (finns ej i repot):
|
||||
```env
|
||||
DATABASE_URL=mysql://user:password@localhost:3306/recipe_app
|
||||
JWT_SECRET=minst-32-tecken-slumpad-sträng
|
||||
MARIADB_ROOT_PASSWORD=
|
||||
MARIADB_DATABASE=recipe_app
|
||||
MISTRAL_API_KEY=
|
||||
NEXT_PUBLIC_APP_URL=https://recept.example.com
|
||||
ADMIN_NADMIN_PASSWORD=
|
||||
ADMIN_PADMIN_PASSWORD=
|
||||
SEED_USER1_PASSWORD=
|
||||
SEED_USER2_PASSWORD=
|
||||
ALLOWED_ORIGIN=https://recept.example.com
|
||||
RECEIPT_TRACE_DECISIONS=0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 📱 **4. Flutter — tokenlagring på webben — MEDIUM**
|
||||
|
||||
**Nuläge:** `WebTokenStorage` använder `SharedPreferences` som mappar till `localStorage` i webbläsaren. JWT i localStorage är åtkomlig via JavaScript och kan stjälas vid XSS.
|
||||
|
||||
**Realistisk åtgärd för en Flutter Web-app:** Det finns inget httpOnly-cookie-alternativ direkt i Flutter Web utan backend-stöd. Om XSS-risken bedöms hög, överväg:
|
||||
1. Backend sätter token som httpOnly cookie vid login — Flutter Web läser aldrig token direkt
|
||||
2. Alternativt: sessionStorage (rensas vid stängt fönster) via JS-interop
|
||||
|
||||
**Nuvarande abstraktionslager är korrekt strukturerat** (`ITokenStorage` → `WebTokenStorage`). Byt ut implementationen vid behov utan att ändra resten av koden.
|
||||
|
||||
> ℹ️ `flutter_secure_storage` fungerar **inte** på web — det är korrekt att web-adaptern använder SharedPreferences.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 🔌 **5. Webhook-säkerhet — LOW (om Gitea-webhooks används)**
|
||||
|
||||
**Nuläge:** Inga webhook-endpoints hittades i koden. Åtgärd krävs endast om Gitea-webhooks kopplas in.
|
||||
|
||||
**Om webhooks läggs till** — validera `X-Gitea-Signature` med `crypto.timingSafeEqual`:
|
||||
|
||||
```typescript
|
||||
// src/webhooks/gitea.controller.ts
|
||||
import { Controller, Post, Headers, Body, RawBodyRequest, Req, UnauthorizedException } from '@nestjs/common';
|
||||
import * as crypto from 'crypto';
|
||||
import { Public } from '../auth/decorators/public.decorator';
|
||||
|
||||
@Controller('webhooks/gitea')
|
||||
export class GiteaController {
|
||||
@Public()
|
||||
@Post()
|
||||
async handleWebhook(
|
||||
@Headers('x-gitea-signature-256') signature: string,
|
||||
@Req() req: RawBodyRequest<Request>,
|
||||
) {
|
||||
const secret = process.env.GITEA_WEBHOOK_SECRET;
|
||||
if (!secret) throw new Error('GITEA_WEBHOOK_SECRET saknas');
|
||||
const hmac = crypto.createHmac('sha256', secret);
|
||||
hmac.update(req.rawBody!);
|
||||
const expected = `sha256=${hmac.digest('hex')}`;
|
||||
if (!crypto.timingSafeEqual(Buffer.from(signature ?? ''), Buffer.from(expected))) {
|
||||
throw new UnauthorizedException('Ogiltig signatur');
|
||||
}
|
||||
// Hantera event...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Lägg till `GITEA_WEBHOOK_SECRET` i `.env.example`.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 🧪 **6. CI/CD-säkerhetstester — MEDIUM**
|
||||
|
||||
> ℹ️ Projektet använder **Gitea** (inte GitHub), så GitHub Actions-workflows i originalplanen fungerar inte.
|
||||
|
||||
Nuvarande CI: tester körs via `npm test` på push (pipeline finns i Gitea).
|
||||
|
||||
**Utöka befintlig pipeline** med:
|
||||
- `npm audit --audit-level=high` — kontrollera kända CVE:er i beroenden
|
||||
- `npx prisma validate` — verifiera schema-integritet
|
||||
- Lägg till `gitleaks` som pre-commit hook lokalt (körs inte i Docker-build)
|
||||
|
||||
```bash
|
||||
# Installera som lokal pre-commit hook
|
||||
npm install -g gitleaks
|
||||
gitleaks protect --staged # kör före varje commit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## ✅ **7. Avslutande Checklista**
|
||||
|
||||
| **Åtgärd** | **Status** | **Ansvarsområde** |
|
||||
| -------------------------------- | ---------- | ----------------------- |
|
||||
| IDOR-skydd: recipes, pantry, meal-plan, receipt-alias | ✅ Klart | Backend |
|
||||
| IDOR-skydd + userId-filtrering för **inventory** | ✅ Klart | Backend (NestJS/Prisma) |
|
||||
| `dist/` tillagd i `.gitignore` | ✅ Klart | Git |
|
||||
| `.env` (utan suffix) i `.gitignore` + `.env.example` | ✅ Klart | Git |
|
||||
| Helmet, CORS, ThrottlerGuard, ValidationPipe | ✅ Klart | Backend |
|
||||
| Flutter: token-abstraktion (`ITokenStorage`) | ✅ Klart | Flutter |
|
||||
| Flutter: httpOnly cookie-alternativ (om XSS är reell risk) | ⬜ LOW | Flutter + Backend |
|
||||
| Gitea webhook-signaturvalidering (om webhooks används) | ⬜ LOW | Backend |
|
||||
| `npm audit` i CI-pipeline | ✅ Klart | CI/CD (Gitea) |
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **8. Prioriterad Ordning för Implementering**
|
||||
|
||||
1. **Inventory IDOR + userId-fält** (CRITICAL) — ✅ KLART 2026-05-07
|
||||
2. **dist/ i .gitignore** (HIGH) — ✅ KLART 2026-05-07
|
||||
3. **bare .env + .env.example** (MEDIUM) — ✅ KLART 2026-05-07
|
||||
4. **npm audit i CI** (MEDIUM) — ✅ KLART 2026-05-07
|
||||
5. **Flutter httpOnly cookies** (LOW — kräver arkitekturförändring)
|
||||
6. **Gitea webhook-validering** (LOW — bara relevant om webhooks används)
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-07: Sammanfattning av senaste säkerhetsförbättringar
|
||||
|
||||
- **Inventory är nu user-scopad:** Alla inventory-operationer kräver och filtrerar på userId i backend (schema, migration, service, controller, tester).
|
||||
- **IDOR-skydd för inventory:** Det är nu omöjligt för användare att läsa eller ändra andras inventarieposter. Tester verifierar att åtkomst nekas vid försök till IDOR.
|
||||
- **.gitignore och deploy-hygien:** backend/dist och backend/tsconfig.tsbuildinfo ignoreras och är ej längre spårade i git. .env och .env.* ignoreras, men .env.example finns och är uppdaterad.
|
||||
- **CI/CD-härdning:** npm audit och prisma validate körs i pipeline. Alla tester och byggen måste passera.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
@@ -0,0 +1,236 @@
|
||||
# Implementeringsplan: "Se kvitto"-Modal för Kvittoimporten
|
||||
|
||||
**Mål**: MVP-vägen för split-view UX – lägg till modal som visar OCR-text från parsade kvittoraderna.
|
||||
**Scope**: 2-3 timmar
|
||||
**Status**: Planering
|
||||
|
||||
---
|
||||
|
||||
## 1. Ändringar i `receipt_import_tab.dart`
|
||||
|
||||
### 1.1 Lägg till knapp "Se kvitto" i header-raden (rad ~745-752)
|
||||
|
||||
**Plats**: Höger om "Välj alla/Avmarkera alla"-knappen
|
||||
|
||||
```dart
|
||||
// Innan: Row med bara "Välj alla"-knapp
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${items.length} rader — tryck för att redigera', style: theme.textTheme.titleSmall),
|
||||
TextButton(...), // "Välj alla/Avmarkera alla"
|
||||
],
|
||||
)
|
||||
|
||||
// Efter: Lägg till "Se kvitto"-knapp
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${items.length} rader — tryck för att redigera', style: theme.textTheme.titleSmall),
|
||||
Row(
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: items.isEmpty ? null : () => _showReceiptPreview(context, items),
|
||||
icon: const Icon(Icons.description_outlined),
|
||||
label: const Text('Se kvitto'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
TextButton(
|
||||
onPressed: () => setState(...), // Befintlig "Välj alla"
|
||||
child: Text(...),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### 1.2 Implementera `_showReceiptPreview`-metod
|
||||
|
||||
Lägg till denna metod i `_ReceiptImportTabState`:
|
||||
|
||||
```dart
|
||||
Future<void> _showReceiptPreview(BuildContext context, List<ParsedReceiptItem> items) async {
|
||||
if (!context.mounted) return;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => _ReceiptPreviewDialog(items: items),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Ny widget: `_ReceiptPreviewDialog`
|
||||
|
||||
Lägg till denna widget **i samma fil** (`receipt_import_tab.dart`), efter `_ReceiptImportResultRow`-klassen:
|
||||
|
||||
```dart
|
||||
class _ReceiptPreviewDialog extends StatelessWidget {
|
||||
final List<ParsedReceiptItem> items;
|
||||
|
||||
const _ReceiptPreviewDialog({required this.items});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text('Kvittotexten i sin helhet'),
|
||||
content: SizedBox(
|
||||
width: 600, // Responsiv bredd på desktop
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Här visas all OCR-parsad text från kvittot. En rad per artikel:',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerLowest,
|
||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
children: items.isEmpty
|
||||
? [TextSpan(text: '(Inga rader)', style: theme.textTheme.bodySmall)]
|
||||
: items
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) {
|
||||
final item = entry.value;
|
||||
final lineNumber = entry.key + 1;
|
||||
final lineText = _formatReceiptLine(item);
|
||||
return TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '$lineNumber. ',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: lineText,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
const TextSpan(text: '\n'),
|
||||
],
|
||||
);
|
||||
})
|
||||
.toList(),
|
||||
),
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Stäng'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _formatReceiptLine(ParsedReceiptItem item) {
|
||||
final parts = <String>[];
|
||||
|
||||
if (item.quantity != null) {
|
||||
parts.add('${item.quantity}');
|
||||
}
|
||||
|
||||
if (item.unit != null) {
|
||||
parts.add(item.unit!);
|
||||
}
|
||||
|
||||
parts.add(item.rawName);
|
||||
|
||||
if (item.price != null) {
|
||||
parts.add('— ${item.price} kr');
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Implementeringssteg (steg-för-steg)
|
||||
|
||||
1. **Läs receipt_import_tab.dart** och identifiera raden med "Välj alla/Avmarkera alla"-knappen
|
||||
2. **Refaktorera Row**: Lägg "Se kvitto"-knapp bredvid befintliga knapp
|
||||
3. **Lägg till `_showReceiptPreview()`-metod** i `_ReceiptImportTabState`
|
||||
4. **Implementera `_ReceiptPreviewDialog`-widget** på slutet av filen
|
||||
5. **Testa**:
|
||||
- Ladda ett kvitto
|
||||
- Klicka "Se kvitto"-knappen
|
||||
- Verifiera att texten är lesbar och formaterad
|
||||
- Testa responsive bredd (dialog behöver minska på mobil)
|
||||
|
||||
---
|
||||
|
||||
## 4. Responsiv förbättring (optional)
|
||||
|
||||
Om dialogen behöver anpassas för mobil:
|
||||
|
||||
```dart
|
||||
// I _ReceiptPreviewDialog.build():
|
||||
final isWide = MediaQuery.of(context).size.width > 600;
|
||||
|
||||
return Dialog(
|
||||
insetPadding: const EdgeInsets.all(16),
|
||||
child: SizedBox(
|
||||
width: isWide ? 600 : double.maxFinite, // Full bredd på mobil
|
||||
// ...
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Långsiktiga förbättringar (Phase 2)
|
||||
|
||||
Se `next_steps_flutter.md` för split-view roadmap:
|
||||
- Horisontell split-view på desktop
|
||||
- Scroll-synkronisering
|
||||
- Tab-fallback på mobil
|
||||
- AI-guiding labels
|
||||
|
||||
---
|
||||
|
||||
## Ärendemal
|
||||
|
||||
**Titel**: "Se kvitto"-modal för kvittoimporten
|
||||
**Branch**: `feat/receipt-preview-modal`
|
||||
**Labels**: `enhancement`, `import-ux`, `phase-1-mvp`
|
||||
**Estimate**: 2-3h
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
@@ -0,0 +1,149 @@
|
||||
# Flutter Performance – Profileringsguide
|
||||
|
||||
## Mål
|
||||
|
||||
| Mätpunkt | Gränsvärde |
|
||||
|---|---|
|
||||
| Frame build-tid (60 Hz) | < 16 ms |
|
||||
| Frame build-tid (120 Hz) | < 8 ms |
|
||||
| Scroll jank (tappade frames) | 0 vid normal scroll |
|
||||
| Minnesfotavtryck (app) | < 200 MB |
|
||||
|
||||
---
|
||||
|
||||
## 1. Starta i profile-läge
|
||||
|
||||
Kör alltid profilmätningar i **profile mode**, inte debug. Debug-läget har JIT-kompilering och extra overhead.
|
||||
|
||||
```bash
|
||||
# Mot fysisk enhet
|
||||
flutter run --profile
|
||||
|
||||
# Mot Chrome (web)
|
||||
flutter run -d chrome --profile
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Flutter DevTools – Öppna
|
||||
|
||||
```bash
|
||||
flutter pub global activate devtools
|
||||
flutter pub global run devtools
|
||||
```
|
||||
|
||||
Eller anslut direkt från terminalen när appen körs i profile mode – Flutter skriver ut en DevTools-URL.
|
||||
|
||||
---
|
||||
|
||||
## 3. Timeline – Mät frame-tider
|
||||
|
||||
1. Öppna **Performance**-fliken i DevTools.
|
||||
2. Klicka **Record**.
|
||||
3. Utför den aktion du vill mäta (t.ex. byt vy, scrolla).
|
||||
4. Klicka **Stop**.
|
||||
5. Granska:
|
||||
- **UI thread** (Dart-kod) – bör vara < 16 ms per frame.
|
||||
- **Raster thread** (GPU) – bör vara < 16 ms per frame.
|
||||
- Röda/gula staplar = jank.
|
||||
|
||||
### Kritiska mätpunkter i appen
|
||||
|
||||
| Scenario | Vad att leta efter |
|
||||
|---|---|
|
||||
| Byta vy (NavigationBar) | Frame-tid vid `StatefulShellRoute`-byte; bör vara < 32 ms totalt |
|
||||
| Scrolla receptlista | Inga röda frames; `GridView.builder` bör recykla element |
|
||||
| Scrolla adminpaneler | `ListView.builder` i embedded-läge; verifiera att ingen `NeverScrollableScrollPhysics` blockerar |
|
||||
| Kvittoimport – kryssa i rad | Endast den berörda raden bör rebuilda (`ConsumerWidget.select`) |
|
||||
| Kvittoimport – "Välj alla" | Batch-uppdatering via `setSelectedForAll` – en enda `state =` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Widget Rebuild-spårning
|
||||
|
||||
Aktivera rebuild-räknare i DevTools under **Inspector → Widget rebuild counts**.
|
||||
|
||||
Alternativt: lägg till tillfällig räknare i en widget:
|
||||
|
||||
```dart
|
||||
int _buildCount = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
debugPrint('${widget.runtimeType} build #${++_buildCount}');
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Förväntade rebuild-mönster efter optimeringar
|
||||
|
||||
- `_ReceiptImportResultRow` med index X ska bara rebuilda när `selected[X]` ändras, inte när andra rader kryssas.
|
||||
- `AppShell` ska inte rebuilda vid vy-byte (StatefulShellRoute bevarar grenar).
|
||||
- Admin-paneler ska inte rebuilda hela listan vid en alias-ändring.
|
||||
|
||||
---
|
||||
|
||||
## 5. Memory Profiler
|
||||
|
||||
1. DevTools → **Memory**-fliken.
|
||||
2. Klicka **Take snapshot** före och efter en tung operation.
|
||||
3. Jämför levande objekt – leta efter läckor (ackumulerade `StreamSubscription`, `Timer`, `Notifier`).
|
||||
|
||||
---
|
||||
|
||||
## 6. flutter analyze + dart fix
|
||||
|
||||
```bash
|
||||
flutter analyze
|
||||
dart fix --apply
|
||||
```
|
||||
|
||||
Åtgärda alla varningar om `const` och onödiga rebuilds.
|
||||
|
||||
---
|
||||
|
||||
## 7. Identifierade optimeringar (genomförda)
|
||||
|
||||
| Område | Åtgärd | Effekt |
|
||||
|---|---|---|
|
||||
| Admin-paneler | Tog bort `NeverScrollableScrollPhysics` + `shrinkWrap` | Scroll fungerar, O(n) layout istället för O(n²) |
|
||||
| Admin alias-lista | `ListView.builder` istället för spread | Virtualiserad lista |
|
||||
| FABs | Explicita `heroTag` på alla FABs | Eliminerar hero-animation-krasch vid vy-byte |
|
||||
| Scrollables | `PageStorageKey` på alla listvy | Scrollposition bevaras vid vy-byte |
|
||||
| Router | `StatefulShellRoute.indexedStack` | Branch-state bevaras; ingen ombyggnad vid tab-byte |
|
||||
| Kvittoimport – resultatlista | `ListView.builder` + `SizedBox` bound height | Virtualiserad; max 620 px synlig |
|
||||
| Kvittoimport – radwidget | `ConsumerWidget` med `provider.select((s) => s?.selected[index])` | Endast ändrad rad rebuildar vid checkbox-toggle |
|
||||
| Kvittoimport – batch-API | `setSelectedForAll`, `setSelectedForIndexes`, `setImportedResult` | En `state =` och en SharedPreferences-skrivning per operation |
|
||||
|
||||
---
|
||||
|
||||
## 8. Snabbtest – Verifiera förbättringar
|
||||
|
||||
```bash
|
||||
# Kör i profile mode och öppna DevTools automatiskt
|
||||
flutter run --profile --devtools-server-address=http://127.0.0.1:9100
|
||||
```
|
||||
|
||||
Kontrollchecklista:
|
||||
|
||||
- [ ] Vy-byte NavigationBar: inga röda frames i Timeline
|
||||
- [ ] Scroll i receptlista: < 2 tappade frames per 100 frames
|
||||
- [ ] Scroll i admin-flikar: fungerar utan lock
|
||||
- [ ] Kvittoimport – checkbox-toggle: rebuild-räknare ökar bara för berörd rad
|
||||
- [ ] Kvittoimport – "Välj alla": en burst av rebuilds (en per rad), inga dubbla
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
@@ -0,0 +1,53 @@
|
||||
# Flutter Frontend - Anvandarguide
|
||||
|
||||
Detta dokument ar for anvandare och operativa testare.
|
||||
Har beskriver vi vad som fungerar i Flutter-klienten och hur den anvands i praktiken.
|
||||
|
||||
## Dokumentstatus (2026-05-03)
|
||||
|
||||
- Fokus: anvandarflode, inte implementation.
|
||||
- Teknisk detaljniva finns i `teknisk_beskrivning_flutter.md`.
|
||||
- Planering och backlog finns i `next_steps_flutter.md`.
|
||||
|
||||
## Vad appen ar
|
||||
|
||||
Flutter-webben ar en klient for Recipe App som kors i Docker och exponeras via Caddy.
|
||||
Den anvands parallellt med Next-frontenden under migrering och verifiering.
|
||||
|
||||
## Senaste forbattringar
|
||||
|
||||
- Kvittoimportens granskningsflode ar klart och stabiliserat.
|
||||
- Pagande kvittoimport sparas i klientens session och kan atertas efter refresh/navigation.
|
||||
- Tolkning av antal/forpackning i kvittorader ar forbattrad, inklusive format som `2st`.
|
||||
- AI-kategoriforslag och produktforslag visas separerat for tydligare val.
|
||||
|
||||
## Aktuella anvandarfloden
|
||||
|
||||
- Inloggning med anvandarnamn och losenord.
|
||||
- Recept: lista, skapa, redigera, radera.
|
||||
- Inventarie och baslager: skapa, redigera, konsumera.
|
||||
- Matplan: veckovy, portionsjustering, inkopslista.
|
||||
- Import: receptimport och kvittoimport med granskningssteg.
|
||||
|
||||
## Kanda begransningar
|
||||
|
||||
- Vissa adminfunktioner och avancerad AI-integration ar planerade men ej fullt migrerade.
|
||||
- Bildimport forutsatter att containrar ar uppdaterade med senaste kod.
|
||||
|
||||
## Felsokning (anvandarniva)
|
||||
|
||||
1. Om sidan visar gammalt beteende efter deploy: hard uppdatering eller inkognito.
|
||||
2. Om inloggning misslyckas: verifiera anvandarnamn/losenord (inte e-post).
|
||||
3. Om importfloden fastnar: rapportera webblasarkonsolens fel till utvecklingsteamet.
|
||||
|
||||
## Relaterade dokument
|
||||
|
||||
- `next_steps_flutter.md` - roadmap och prioriteringar.
|
||||
- `teknisk_beskrivning_flutter.md` - teknisk referens for drift/utveckling.
|
||||
- `../README.md` - overgripande produktinformation.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Nasta steg: Flutter-migrering
|
||||
|
||||
Detta dokument ar Flutter-teamets roadmap och prioriteringslista.
|
||||
All historik och implementationdetaljer finns i `teknisk_beskrivning_flutter.md`.
|
||||
|
||||
## Dokumentstatus (2026-05-03)
|
||||
|
||||
- Fokus: aktiv planering framat.
|
||||
- Endast en roadmap for Flutter for att undvika dubbletter.
|
||||
|
||||
## Klart senaste sessionerna
|
||||
|
||||
- Fas 6b: granskningsflode for kvittoimport (edit, destination, merge, spara).
|
||||
- Fas 6c: separering av AI-kategorichip och produktforslagschip.
|
||||
- Fas 6d: klientpersistens for pagande kvittoimport + forbattrad antal/forpackningsinferens.
|
||||
|
||||
## Pagande arbete
|
||||
|
||||
- Robust bildimport och diagnostik i drift.
|
||||
- Aliasstrategi i kvittoimport (hybrid user-scope + global fallback via admin).
|
||||
- Utokad adminfunktionalitet i Flutter-sparet.
|
||||
|
||||
## Prioriterade nasta steg
|
||||
|
||||
1. **Kvitto-import UX förbättring (Split-view långsiktigt)**
|
||||
- MVP (kort sikt): Lägg till "Se kvitto"-modal som visar full OCR-text från parsade rader
|
||||
- Knapp i radlist-header, öppnar dialog med ScrollableText
|
||||
- Enkelt UI, höga UX-vinster
|
||||
- Implementering: ~2h
|
||||
- Långsiktigt (Phase 2): Split-view med scroll-synkronisering
|
||||
- Desktop: Horisontell split (kvitto-text vänster, radlista höger)
|
||||
- Tablet/Mobil: Tab-based fallback (radlista standard, "Se kvitto"-tab för kontext)
|
||||
- Scroll-sync mellan text och rader (om rad 3 är synlig, visa motsvarande text)
|
||||
- AI-guiding labels ("Denna rad matchade mejeri automatiskt")
|
||||
- Implementering: ~8h
|
||||
|
||||
2. Verifiera bildimport och felhantering end-to-end i testmiljo.
|
||||
3. Implementera alias-inlarning vid manuell korrigering i importflodet.
|
||||
4. Forbattra UI/UX i granskningsfloden for kvittoimport.
|
||||
5. Fortsatt migrering av kvarvarande adminfloden.
|
||||
6. Lokalisera kvarvarande delar i import- och inventarievyer.
|
||||
|
||||
## Viktiga beslut
|
||||
|
||||
- Flutter ar separat klient mot befintliga API-kontrakt.
|
||||
- Ingen klientspecifik speciallogik for datamatchning; backend ar sanningskalla.
|
||||
- Next-frontenden kor parallellt tills verifierad parity ar uppnadd.
|
||||
|
||||
## Relaterade dokument
|
||||
|
||||
- `README.md` - anvandarperspektiv.
|
||||
- `teknisk_beskrivning_flutter.md` - teknisk referens.
|
||||
- `../NEXT_STEPS.md` - overgripande roadmap for hela produkten.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
@@ -0,0 +1,128 @@
|
||||
# Teknisk Beskrivning - Flutter Frontend
|
||||
|
||||
Detta dokument ar teknisk referens for systemadministratorer och programmerare.
|
||||
Mallet ar en sammanhallen teknisk sanning utan duplicerade roadmap- eller anvandarsektioner.
|
||||
|
||||
## Dokumentstatus (2026-05-03)
|
||||
|
||||
- Fokus: arkitektur, drift och kanda gotchas.
|
||||
- User guide finns i `README.md`.
|
||||
- Planering finns i `next_steps_flutter.md`.
|
||||
|
||||
## Viktigt om miljo
|
||||
|
||||
Anvand inte absoluta Windows-sokvagar i kod eller scripts.
|
||||
Bygg och drift sker pa Linux/Ubuntu i containeriserad miljo.
|
||||
|
||||
## Senaste tekniska tillagg
|
||||
|
||||
- Sessionpersistens i klienten for pagande kvittoimport.
|
||||
- Forbattrad inferens for antal/forpackning (inklusive uttryck som `2st`).
|
||||
- Tydlig separation i UI mellan AI-kategoriforslag och produktforslag.
|
||||
- Ingen server-side persistens introducerad for kvittosession.
|
||||
- Receptskapande: review-steg med redigerbara falt for quantity/unit/note per ingrediens.
|
||||
- Receptskapande: tydlig varning i UI nar ingen produktmatchning hittas for inkluderad ingrediens.
|
||||
- Receptskapande: stod for alternativa ingredienser i parse-svar (ex. "ris eller couscous") och visning i granskningsvyn.
|
||||
- Global kopierbar feltext: copybar error-snackbar + SelectionArea pa appniva for battre felsokning/stod.
|
||||
|
||||
## Arkitektur
|
||||
|
||||
### Lager
|
||||
- Presentation: `flutter/lib/features/*/presentation`
|
||||
- State/Application: Riverpod providers/notifiers i `flutter/lib/features/*/data`
|
||||
- Data/API: `flutter/lib/core/api`
|
||||
- Platform abstraction: token storage i `flutter/lib/core/platform`
|
||||
|
||||
### Routing
|
||||
- GoRouter i `flutter/lib/core/router/app_router.dart`
|
||||
- ShellRoute for huvudnavigation
|
||||
- Fullscreen-routes for skapa/redigera/detalj
|
||||
|
||||
### Auth
|
||||
- Login via `POST /api/auth/login`
|
||||
- Token-falt: `accessToken`
|
||||
- Auth-gate i router med redirect logik
|
||||
- `guardedApiCall()` hanterar logout vid 401
|
||||
|
||||
## Sakerhetsstatus (2026-05-07)
|
||||
|
||||
Denna sektion sammanfattar sakerhetsfunktioner som ar implementerade i Flutter-klienten och hur de fungerar tekniskt.
|
||||
|
||||
### Implementerat i Flutter
|
||||
|
||||
- Auth-gating i routing:
|
||||
- Router-lagret stoppar navigation till skyddade vyer utan token/session.
|
||||
- Om backend returnerar 401 i ett skyddat API-flode hanterar `guardedApiCall()` detta och triggar logout/omdirigering.
|
||||
- Token-lagring via plattformsabstraktion:
|
||||
- `ITokenStorage` definierar kontraktet (`getToken/saveToken/deleteToken`).
|
||||
- Web-implementation (`WebTokenStorage`) lagrar token i `SharedPreferences` (web: localStorage).
|
||||
- Arkitekturen gor att mobil implementation kan bytas till `flutter_secure_storage` utan att ovrig appkod andras.
|
||||
- Minimerad klientside-auktorisering:
|
||||
- Flutter forlitar sig pa backend som auktoritetskalla for access-kontroll.
|
||||
- Klienten skickar bearer-token men gor inte egen resurstagarskapslogik som kan divergera fran serverregler.
|
||||
- Kontraktsstyrd API-hantering:
|
||||
- API-lagret accepterar 2xx pa importanrop och centraliserar felhantering.
|
||||
- Minskar risken for ad-hoc felhantering i UI och inkonsekvent beteende vid auth-fel.
|
||||
|
||||
### Viktig begransning (web)
|
||||
|
||||
- Flutter Web kan inte anvanda `httpOnly` cookies enbart pa klientsidan.
|
||||
- Nuvarande lagring i localStorage ar en praktisk kompromiss for web och innebar att XSS-hardening pa frontend och strict backend-headers/CSP ar fortsatt viktiga.
|
||||
|
||||
## API- och kontraktsprinciper
|
||||
|
||||
- Flutter foljer backend-kontrakt, ingen lokal speciallogik for matchning.
|
||||
- 2xx-svar accepteras generellt i importanrop (inte hardkodning till enbart 200).
|
||||
- Kvittoimport och receptimport ar separata floden med olika svarstyper.
|
||||
|
||||
## Kvittoimport - tekniska noter
|
||||
|
||||
- Granskningsflode med radredigering och destination (inventarie/baslager).
|
||||
- Produktval stoder tradbaserat kategori -> produkt-flode.
|
||||
- AI-kategorihint ar endast ett forslag; slutanvandaren validerar i granskningssteget.
|
||||
|
||||
## Drift och deploy
|
||||
|
||||
### Bygg och start (Flutter)
|
||||
```bash
|
||||
docker compose -f compose.yml -f compose.flutter.yml build recipe-flutter
|
||||
docker compose -f compose.yml -f compose.flutter.yml up -d --no-deps recipe-flutter
|
||||
```
|
||||
|
||||
### Viktiga verifieringar
|
||||
- Compose merge: `docker compose -f compose.yml -f compose.flutter.yml config`
|
||||
- Reachability via proxy/Caddy
|
||||
- Runtime-loggar for importfloden vid felsokning
|
||||
|
||||
## Kanda fallgropar
|
||||
|
||||
- PDF-mime kan komma som `application/octet-stream` i Flutter Web.
|
||||
- Orphan-varning ar normal nar huvudstack och Flutter-stack kors separat.
|
||||
- Cache/service worker kan visa gammal frontend efter deploy.
|
||||
- Vid massersattningar i Dart: verifiera parentesbalans i `showSnackBar(...)`-anrop innan build, annars faller web-kompilering med syntaxfel.
|
||||
|
||||
## Relaterade dokument
|
||||
|
||||
- `README.md` - anvandarguide.
|
||||
- `next_steps_flutter.md` - aktiv planering.
|
||||
- `../TEKNISK_BESKRIVNING.md` - backend/systemovergripande teknik.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
@@ -0,0 +1,266 @@
|
||||
This file has been removed as all relevant information has been migrated.
|
||||
# Prisma P3009 recovery (MySQL, migrationer)
|
||||
|
||||
**Problem:**
|
||||
Prisma migrationer kan fastna i failed state (P3009) om en migration körts med fel SQL-citering (t.ex. "User" istället för `User` i MySQL) eller om deploy avbryts mitt i en migrering.
|
||||
|
||||
**Symptom:**
|
||||
```
|
||||
migrate found failed migrations in the target database, new migrations will not be applied. The `20260506144000_add_ai_engine_enabled` migration ... failed
|
||||
```
|
||||
|
||||
**Lösning/playbook:**
|
||||
1. Rätta migrationsfilen så att den använder backticks (`) för tabell- och kolumnnamn (MySQL-stil).
|
||||
2. Kör:
|
||||
```bash
|
||||
docker exec recipe-api sh -lc "cd /app && npx prisma migrate resolve --rolled-back 20260506144000_add_ai_engine_enabled --schema prisma/schema.prisma"
|
||||
docker exec recipe-api sh -lc "cd /app && npx prisma migrate deploy --schema prisma/schema.prisma"
|
||||
```
|
||||
3. Om deploy klagar på duplicate column (dvs kolumnen finns redan):
|
||||
```bash
|
||||
docker exec recipe-api sh -lc "cd /app && npx prisma migrate resolve --applied 20260506144000_add_ai_engine_enabled --schema prisma/schema.prisma"
|
||||
docker exec recipe-api sh -lc "cd /app && npx prisma migrate deploy --schema prisma/schema.prisma"
|
||||
```
|
||||
4. Verifiera status:
|
||||
```bash
|
||||
docker exec recipe-api sh -lc "cd /app && npx prisma migrate status --schema prisma/schema.prisma"
|
||||
```
|
||||
|
||||
**Lessons learned:**
|
||||
- Kontrollera alltid SQL-citering i migrationsfiler (MySQL kräver backticks, inte dubbla citattecken).
|
||||
- Vid P3009: använd `migrate resolve` för att markera migrationen som rolled-back eller applied beroende på DB-läge.
|
||||
- Kör alltid migrationskommandon i rätt container/miljö för att undvika env- och version-mismatch.
|
||||
|
||||
# Session 2026-05-06: Migreringar för user-scoped AI och premium
|
||||
|
||||
Denna session:
|
||||
- Lade till `aiEngineEnabled` på User i Prisma-schema och migrerade databasen (manuell SQL vid behov).
|
||||
- Implementerade endpoint och logik för admin att toggla AI per användare.
|
||||
- Säkerställde att alla AI-funktioner är user-scoped och premiumstyrda.
|
||||
- Lessons learned: Vid DB-connectivity-problem krävs ibland manuell migration och resolve (se driftsekvens i TEKNISK_BESKRIVNING.md).
|
||||
|
||||
Se även:
|
||||
- [TEKNISK_BESKRIVNING.md](TEKNISK_BESKRIVNING.md) för drift- och migreringsdetaljer.
|
||||
|
||||
# Migrering: Import-funktion → microservice-importer
|
||||
|
||||
## Status: ✅ GENOMFÖRD 2026-04-30
|
||||
|
||||
## Dokumentstatus (2026-05-03)
|
||||
|
||||
### Målgrupp
|
||||
Detta dokument är för systemadministratörer och utvecklare som ansvarar för integrationen mellan recipe-app och microservice-importer.
|
||||
|
||||
### Tillägg efter genomförd migrering
|
||||
- Kvittoparsningens regelbaserade tolkning har förbättrats för multipack och enheter.
|
||||
- Brödrelaterade kategoriregler och contradiction guards har utökats för högre träffsäkerhet.
|
||||
- Klientens kvittosession i Flutter är nu persistent utan att ändra backendkontrakt eller införa serverlagring av sessionen.
|
||||
- Kategoriträdet i seed-data har utökats med `Korvbröd` under `Fastfoodbröd`.
|
||||
- **PDF-fix (2026-05-03):** `pdf-parse` använder `require()` istället för ESM-import; `pdfjs-dist/legacy/build/pdf.js` är fallback vid parsningsfel — löser `DOMMatrix is not defined` i Node.js Alpine-miljö.
|
||||
- **Felkods-forwarding (2026-05-03):** `receipt-import.service.ts` returnerar nu `ServiceUnavailableException` (503) vid 503/429 från importer-api istället för `BadRequestException` (400).
|
||||
- **Reproducerbart bygge (2026-05-03):** `package-lock.json` spåras i git; Dockerfile för importer-api kör `npm ci`.
|
||||
- **AI-optimering (2026-05-03):** `looksLikeReceiptProductLine()` filtrerar PDF-rader utan siffra innan Mistral-anrop — minskar onödiga API-anrop vid kvittoimport.
|
||||
|
||||
- **Scope:** quick-import, parse-markdown, receipt-import
|
||||
- **Arkitektur:** Backend-till-backend — recipe-app NestJS-backend anropar microservice-importer internt via HTTP. Frontend ändras inte.
|
||||
- **OCR:** Implementerat i microservice-importer (tesseract.js + Alpine apk-paket)
|
||||
- **Infra:** `importer-api`-tjänst i `recipe-app/compose.yml`, port 3001 intern
|
||||
- **Driftsatt:** 2026-04-30, alla containers Healthy
|
||||
|
||||
---
|
||||
|
||||
## Fas 1 — Utöka microservice-importer ✅
|
||||
|
||||
**1. OCR-stöd och multipart i quick-import** ✅
|
||||
- `QuickImportService.importFromUpload()` tillagd — hanterar PDF (pdf-parse) och bild (tesseract.js)
|
||||
- `quick-import.controller.ts` utökat med `FileInterceptor`, `@HttpCode(200)`
|
||||
- `Dockerfile` uppdaterad: `apk add tesseract-ocr tesseract-ocr-data-swe tesseract-ocr-data-eng`
|
||||
|
||||
**2. `imageUrl` i quick-import-svaret** ✅
|
||||
- `imageUrl?: string` tillagd i `ParsedRecipe`-interfacet (`base.parser.ts`)
|
||||
- ICA-parsern extraherar nu `recipe.image` (string/array/objekt-varianter)
|
||||
- `QuickImportResult` utökad med `imageUrl?`, `imageWarning?`, source `'image'`
|
||||
- `normalizeImageUrl()` hanterar protokollrelativa URL:er (`//cdn.ica.se/...`)
|
||||
|
||||
**3. Ny `ReceiptParsingModule`** ✅
|
||||
- `backend/src/receipt-parsing/receipt-parsing.service.ts` — Mistral AI-parsning av kvitto (bild/PDF)
|
||||
- `backend/src/receipt-parsing/receipt-parsing.controller.ts` — `POST /api/receipt-import/parse`, `@HttpCode(200)`, tillåter `application/octet-stream`
|
||||
- `backend/src/receipt-parsing/receipt-parsing.module.ts`
|
||||
- Registrerad i `app.module.ts`
|
||||
|
||||
**4. Health-endpoint** ✅
|
||||
- `GET /api/health` → `{status: "ok"}` inline i `app.module.ts`
|
||||
|
||||
**5. Bugfixar i document-service** ✅
|
||||
- `document-service.module.ts`: korrigerade importvägar + klassnamn (`DocumentImportModule` → `DocumentServiceModule`)
|
||||
- `services/document-import.service.ts`: parsersökväg `./parsers/` → `../parsers/`
|
||||
- Borttagna dubbletter: `services/web-scraping.module.ts`, `services/document-service.module.ts`
|
||||
|
||||
---
|
||||
|
||||
## Fas 2 — Anpassa recipe-app backend ✅
|
||||
|
||||
**5. Refaktorera `QuickImportService`** ✅
|
||||
- All lokal parsning (ICA, pdf-parse, tesseract) borttagen
|
||||
- Delegerar URL-import: `POST importer-api:3001/api/quick-import` (JSON)
|
||||
- Delegerar filuploading: `POST importer-api:3001/api/quick-import` (FormData, `new Uint8Array(file.buffer)`)
|
||||
- `downloadAndOptimizeImage()` behålls lokalt (körs efter microservice returnerat `imageUrl`)
|
||||
- `IMPORTER_SERVICE_URL` env-variabel med fallback `http://importer-api:3001`
|
||||
|
||||
**6. Refaktorera `ReceiptImportService`** ✅
|
||||
- AI-parsning (Mistral, pdf-parse) borttagen ur recipe-app
|
||||
- Delegerar till `POST importer-api:3001/api/receipt-import/parse` (FormData)
|
||||
- `matchProducts()` och `enrichWithAiCategories()` behålls (DB-krav)
|
||||
- `RECEIPT_IMPORT_MODEL`-konstanten flyttad till `ai.controller.ts` (lokal konstant)
|
||||
|
||||
**7. Refaktorera `RecipesService.parseMarkdown()`** ✅
|
||||
- Delegerar markdown-parsning till `POST importer-api:3001/api/recipes/parse-markdown`
|
||||
- Fallback till lokal `parseRecipeMarkdown()` vid driftavbrott
|
||||
- Levenshtein-produktmatchning behålls lokalt
|
||||
|
||||
---
|
||||
|
||||
## Fas 3 — Infrastruktur ✅
|
||||
|
||||
**8. `importer-api` i `recipe-app/compose.yml`** ✅
|
||||
- Build-context: `../microservice-importer`, dockerfile `backend/Dockerfile`
|
||||
- Image: `recipe-importer-api:local`, `pull_policy: never`
|
||||
- Nätverk: `recipe-internal` (ej exponerad externt)
|
||||
- Env: `MISTRAL_API_KEY`, `PORT=3001`
|
||||
- Healthcheck: `wget -qO- http://127.0.0.1:3001/api/health`
|
||||
- `recipe-api` får `depends_on: importer-api: condition: service_healthy`
|
||||
|
||||
---
|
||||
|
||||
## Relevanta filer som ändrades
|
||||
|
||||
| Fil | Förändring |
|
||||
|---|---|
|
||||
| `microservice-importer/backend/src/web-scraping-service/parsers/base.parser.ts` | `imageUrl?` i `ParsedRecipe` |
|
||||
| `microservice-importer/backend/src/web-scraping-service/parsers/ica.parser.ts` | Extraherar `recipe.image` |
|
||||
| `microservice-importer/backend/src/web-scraping-service/services/quick-import.service.ts` | Omskriven: OCR, PDF, imageUrl, importFromUpload |
|
||||
| `microservice-importer/backend/src/web-scraping-service/controllers/quick-import.controller.ts` | FileInterceptor, HttpCode(200) |
|
||||
| `microservice-importer/backend/src/web-scraping-service/web-scraping.module.ts` | Fixade importvägar + klassnamn |
|
||||
| `microservice-importer/backend/src/document-service/document-service.module.ts` | Fixade importvägar + klassnamn |
|
||||
| `microservice-importer/backend/src/document-service/services/document-import.service.ts` | Fixad parsersökväg |
|
||||
| `microservice-importer/backend/src/receipt-parsing/` | Ny modul (service, controller, module) |
|
||||
| `microservice-importer/backend/src/app.module.ts` | ReceiptParsingModule + HealthController |
|
||||
| `microservice-importer/backend/Dockerfile` | apk add tesseract-ocr |
|
||||
| `recipe-app/backend/src/quick-import/quick-import.service.ts` | Delegerar till importer-api |
|
||||
| `recipe-app/backend/src/receipt-import/receipt-import.service.ts` | AI-del delegeras, matchning behålls |
|
||||
| `recipe-app/backend/src/recipes/recipes.service.ts` | parseMarkdown delegeras, matchning behålls |
|
||||
| `recipe-app/backend/src/ai/ai.controller.ts` | RECEIPT_IMPORT_MODEL lokal konstant |
|
||||
| `recipe-app/compose.yml` | importer-api-tjänst tillagd |
|
||||
|
||||
---
|
||||
|
||||
## Avgränsningar (oförändrade)
|
||||
|
||||
- **Frontend ändras inte** — samma proxy-routes, samma API-kontrakt
|
||||
- **Auth stannar i recipe-app backend** — microservice-importer exponeras bara internt
|
||||
- **Bildoptimering** behålls i recipe-app (`downloadAndOptimizeImage` vid `RecipesService.create()`)
|
||||
- `matchProducts()` och `enrichWithAiCategories()` stannar i recipe-app (DB-krav)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Fas 1 — Utöka microservice-importer
|
||||
|
||||
*Steg 1–3 är oberoende och kan utföras parallellt.*
|
||||
|
||||
**1. Lägg till OCR-stöd (tesseract.js)**
|
||||
Ny `ImageParser` i `backend/src/web-scraping-service/parsers/`. Controllern
|
||||
`quick-import.controller.ts` utökas att acceptera `multipart/form-data` för
|
||||
bilder vid sidan av JSON-body för URL-anrop.
|
||||
|
||||
**2. Lägg till `imageUrl` i quick-import-svaret**
|
||||
`quick-import.service.ts` returnerar idag `{ markdown, source }`. Komplettera
|
||||
med `imageUrl?` (original-URL från skrapad sida).
|
||||
|
||||
**3. Ny `ReceiptParsingModule` – stateless kvittoparsning**
|
||||
Ny modul `backend/src/receipt-parsing/` med endpoint `POST /api/receipt-import/parse`.
|
||||
- PDF → text via `pdf-parse`; bild → base64
|
||||
- Anropar Mistral AI med kvitto-prompt
|
||||
- Returnerar: `[{ rawName, quantity, unit, price, brand, origin }]`
|
||||
- Ingen databaskoppling — rent stateless
|
||||
|
||||
---
|
||||
|
||||
## Fas 2 — Anpassa recipe-app backend
|
||||
|
||||
*Beror på Fas 1. Steg 5–7 kan utföras parallellt.*
|
||||
|
||||
**4. Lägg till `HttpModule` + `IMPORTER_SERVICE_URL`**
|
||||
recipe-app backend registrerar NestJS:s `HttpModule` (axios-wrapper).
|
||||
`IMPORTER_SERVICE_URL` sätts som env-variabel (`http://importer-api:3001` i Docker).
|
||||
|
||||
**5. Refaktorera `QuickImportService`**
|
||||
Ta bort lokal ICA-parsning, pdf-parse och tesseract — anropa istället
|
||||
microservice-importer `POST /api/quick-import` (eller `POST /api/document-import`
|
||||
för PDF). `QuickImportModule` behåller sin controller och DTO (API-kontrakt oförändrat).
|
||||
|
||||
**6. Refaktorera `ReceiptImportService`**
|
||||
- AI-parsning → delegeras till `POST $IMPORTER_URL/api/receipt-import/parse`
|
||||
- Produktmatchning (Levenshtein mot `Product`, `ReceiptAlias`) — behålls i recipe-app (DB-krav)
|
||||
- Slår ihop och returnerar samma svar som idag till frontend
|
||||
|
||||
**7. Refaktorera `RecipesService.parseMarkdown()`**
|
||||
- Anropar `POST $IMPORTER_URL/api/recipes/parse-markdown` → `{ name, ingredients[], ... }`
|
||||
- Kör befintlig Levenshtein-produktmatchning mot `Product`-tabellen i recipe-app
|
||||
- Returnerar sammansatt svar — API-kontraktet mot frontend oförändrat
|
||||
|
||||
**8. Ta bort lokala parsningsberoenden**
|
||||
Ta bort `pdf-parse`, `tesseract.js`, `node-fetch` etc. ur recipe-app backend
|
||||
`package.json` när steg 5–7 är verifierade.
|
||||
|
||||
---
|
||||
|
||||
## Fas 3 — Infrastruktur
|
||||
|
||||
*Kan påbörjas parallellt med Fas 1.*
|
||||
|
||||
**9. Länka microservice-importer i recipe-app:s Docker Compose**
|
||||
Lägg till `importer-api`-tjänst i `recipe-app/compose.yml` (byggs från
|
||||
`../microservice-importer/backend`). Delar `recipe-network` med recipe-app
|
||||
backend. Sätt `IMPORTER_SERVICE_URL=http://importer-api:3001` i recipe-app
|
||||
backend-tjänstens env.
|
||||
|
||||
---
|
||||
|
||||
## Relevanta filer
|
||||
|
||||
| Fil | Förändring |
|
||||
|---|---|
|
||||
| `microservice-importer/backend/src/web-scraping-service/` | Ny ImageParser, imageUrl i svar |
|
||||
| `microservice-importer/backend/src/` | Ny `receipt-parsing/` modul |
|
||||
| `recipe-app/backend/src/quick-import/quick-import.service.ts` | Ersätt lokal parsning med HTTP-anrop |
|
||||
| `recipe-app/backend/src/receipt-import/receipt-import.service.ts` | AI-del delegeras, matchning behålls |
|
||||
| `recipe-app/backend/src/recipes/recipes.service.ts` | parseMarkdown delegeras, matchning behålls |
|
||||
| `recipe-app/backend/src/app.module.ts` | Registrera HttpModule |
|
||||
| `recipe-app/backend/package.json` | Ta bort pdf-parse, tesseract.js |
|
||||
| `recipe-app/compose.yml` | Lägg till importer-api tjänst |
|
||||
| `recipe-app/frontend/` | **Ändras inte** |
|
||||
|
||||
---
|
||||
|
||||
## Verifiering
|
||||
|
||||
1. `POST /api/quick-import` (recipe-app backend) med ICA-URL → samma svar som idag
|
||||
2. `POST /api/quick-import` med PDF-fil → samma svar
|
||||
3. `POST /api/recipes/parse-markdown` med markdown → ingredienser med produkt-ID:n
|
||||
4. `POST /api/receipt-import` med kvittobild → matchade items med DB-produkt-ID:n
|
||||
5. Autentisering fungerar (hanteras av recipe-app backend som tidigare)
|
||||
6. `docker compose up` startar microservice-importer som intern tjänst
|
||||
|
||||
---
|
||||
|
||||
## Avgränsningar
|
||||
|
||||
- **Frontend ändras inte** — samma proxy-routes, samma API-kontrakt
|
||||
- **Auth stannar i recipe-app backend** — microservice-importer exponeras bara internt på Docker-nätverket
|
||||
- **Bildoptimering vid sparande** behålls i recipe-app (sker vid `RecipesService.create()`, inte vid import)
|
||||
- `receipt-import` splittad: AI-del → microservice, produktmatchning + DB → recipe-app backend
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Plan for produktlansering
|
||||
|
||||
Detta dokument ar en releasechecklista.
|
||||
Det kompletterar `NEXT_STEPS.md` och ska inte duplicera backloggen.
|
||||
|
||||
## Dokumentstatus (2026-05-03)
|
||||
|
||||
- Malgrupp: produktagare, systemadministratorer, utvecklingsteam.
|
||||
- Fokus: vad som maste vara verifierat innan release.
|
||||
|
||||
## 1. Sakerhet och data
|
||||
|
||||
- [ ] Kansliga uppgifter krypterade enligt beslutad modell.
|
||||
- [ ] Rate limiting aktiv pa relevanta API/AI-endpoints.
|
||||
- [ ] Secret-hantering verifierad (inga hardkodade hemligheter).
|
||||
- [ ] Roll- och accesskontroller testade i praktiken.
|
||||
|
||||
## 2. DevOps och stabilitet
|
||||
|
||||
- [ ] CI/CD for build, test och deploy pa plats.
|
||||
- [ ] Migreringar + seedning kor konsekvent vid release.
|
||||
- [ ] Health checks och loggning verifierade.
|
||||
- [ ] Backup/restore testad for datavolymer.
|
||||
|
||||
## 3. Kvalitet och test
|
||||
|
||||
- [ ] Kritiska floden har testtackning (auth, import, CRUD, AI).
|
||||
- [ ] Minst en end-to-end verifiering i testmiljo per release.
|
||||
- [ ] DTO-validering och felhantering kontrollerad.
|
||||
|
||||
## 4. Funktionell releaseklarhet
|
||||
|
||||
- [ ] Kvittoimport fungerar end-to-end med granskningssteg.
|
||||
- [ ] User-scoped produktmodell verifierad med flera testanvandare.
|
||||
- [ ] Kategoritrad seedat och validerat i aktuell miljo.
|
||||
- [ ] Bildimport och fallbackfloden fungerar i driftmiljo.
|
||||
|
||||
## 5. Riskhantering
|
||||
|
||||
- [ ] AI-kostnad, timeout och fallback beteende verifierat.
|
||||
- [ ] Ingen osynk mellan migrationer och seedskript.
|
||||
- [ ] Kanda release-risker dokumenterade med ansvarig agare.
|
||||
|
||||
## Relaterade dokument
|
||||
|
||||
- `NEXT_STEPS.md` - overgripande prioriteringar.
|
||||
- `TEKNISK_BESKRIVNING.md` - teknisk implementation.
|
||||
- `flutter/next_steps_flutter.md` - Flutter-specifik leveransplan.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
@@ -0,0 +1,104 @@
|
||||
# Plan för systematisk backend-review och optimering
|
||||
|
||||
## Mål
|
||||
1. Minska komplexitet och duplicering.
|
||||
2. Förbättra prestanda och stabilitet.
|
||||
3. Göra koden enklare att underhålla och vidareutveckla.
|
||||
4. Införa kvalitetsgrindar så förbättringar håller över tid.
|
||||
|
||||
## Arbetssätt
|
||||
1. Jobba i små, säkra iterationer per modul/domän.
|
||||
2. Mät före och efter varje förändring.
|
||||
3. Lås upp förbättringar med tester och CI-gates.
|
||||
4. Prioritera förändringar med hög effekt och låg risk först.
|
||||
|
||||
## Fas 1: Baslinje och kartläggning (1 vecka)
|
||||
1. Inventera backend per modul:
|
||||
- Endpoints, tjänster, databasaccess, externa integrationer.
|
||||
2. Sätt baslinjemätningar:
|
||||
- Responstider per kritisk endpoint (p50/p95), felgrad, DB-latens.
|
||||
- Nuvarande testtäckning per modul.
|
||||
3. Skapa hotspot-lista:
|
||||
- Långa metoder, hög cyclomatic complexity, duplicerad logik, N+1-frågor.
|
||||
4. Leverabel:
|
||||
- Prioriterad backlog med topp 10 förbättringsområden.
|
||||
|
||||
## Fas 2: Snabba vinster och kodhygien (1-2 veckor)
|
||||
1. Standardisera felhantering:
|
||||
- Enhetlig exception mapping och API-felmodell.
|
||||
2. Rensa duplicerad kod:
|
||||
- Flytta gemensam logik till tydliga utilities/domänservices.
|
||||
3. Förbättra validering:
|
||||
- Konsekvent DTO/valideringslager in och ut.
|
||||
4. Inför striktare lint-regler:
|
||||
- Max function length, complexity-tak, no-dead-code.
|
||||
5. Leverabel:
|
||||
- Minskad kodvolym i hotspots och jämnare kodstandard.
|
||||
|
||||
## Fas 3: Arkitektur-förenkling (2-3 veckor)
|
||||
1. Tydlig separering av lager:
|
||||
- Controller = transport.
|
||||
- Service = affärslogik.
|
||||
- Repository/data layer = persistens.
|
||||
2. Minska beroendekoppling:
|
||||
- Ta bort korsberoenden mellan moduler.
|
||||
3. Inför tydliga domängränser:
|
||||
- En modul ska kunna förstås utan att läsa flera andra.
|
||||
4. Leverabel:
|
||||
- Enklare call-flöden och färre starkt kopplade beroenden.
|
||||
|
||||
## Fas 4: Databas och prestanda (1-2 veckor)
|
||||
1. Granska tunga queries:
|
||||
- N+1, överhämtning, saknade index, ineffektiva joins.
|
||||
2. Förbättra dataåtkomst:
|
||||
- Standard för pagination/filtering.
|
||||
- Selektiv hämtning av fält.
|
||||
3. Caching där det är motiverat:
|
||||
- Endast för dyra och frekventa läsningar.
|
||||
4. Leverabel:
|
||||
- Mätbar förbättring i p95 och minskad DB-belastning.
|
||||
|
||||
## Fas 5: Teststrategi och regressionsskydd (1-2 veckor, löpande)
|
||||
1. Lägg tester där risk och affärsvärde är högst:
|
||||
- Kritiska use cases först.
|
||||
2. Balans i testpyramiden:
|
||||
- Fler enhetstester för domänlogik.
|
||||
- Fokuserade integrations- och API-tester för flöden.
|
||||
3. Kontrakttester för externa integrationer.
|
||||
4. Leverabel:
|
||||
- Högre täckning i kritiska moduler och färre regressionsbuggar.
|
||||
|
||||
## Fas 6: Säkerhet och driftbarhet (parallellt)
|
||||
1. Säkerhetsgranskning:
|
||||
- Input-validering, auth/role-kontroller, secret-hantering.
|
||||
- Inventory är nu user-scopad och IDOR-skyddad: Alla inventory-operationer kräver och filtrerar på userId i backend (schema, migration, service, controller, tester). Tester verifierar att åtkomst nekas vid försök till IDOR.
|
||||
2. Driftbarhet:
|
||||
- Strukturerad loggning, korrelations-id, tydligare metrics.
|
||||
3. Resiliens:
|
||||
- Timeout/retry/circuit-breaker för externa beroenden.
|
||||
4. Leverabel:
|
||||
- Färre driftincidenter och enklare felsökning.
|
||||
|
||||
## Kvalitetsgrindar i CI
|
||||
1. Build och lint måste passera.
|
||||
2. Tester måste passera.
|
||||
3. Miniminivå för täckning i ändrade moduler.
|
||||
4. Blockera PR vid ökad komplexitet över satt tröskel.
|
||||
5. Enkel performance-smoke på kritiska endpoints.
|
||||
|
||||
## Prioriteringsmodell för varje förbättring
|
||||
1. Effekt: prestanda, stabilitet, underhållbarhet.
|
||||
2. Risk: regressionsrisk och driftsrisk.
|
||||
3. Insats: utvecklingstid.
|
||||
4. Välj först: hög effekt + låg/medel risk + låg/medel insats.
|
||||
|
||||
## Definition of Done
|
||||
1. Kritiska endpoints har förbättrad p95.
|
||||
2. Topp-hotspots är refaktorerade eller borttagna.
|
||||
3. Kodduplicering reducerad i prioriterade moduler.
|
||||
4. Testskydd finns för alla kritiska flöden.
|
||||
5. CI-gates förhindrar att kvaliteten glider tillbaka.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
|
||||
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||
Reference in New Issue
Block a user