# Microservice Importer Standalone-tjänst för snabbimport av recept från webben. Extraherar receptdata (namn, beskrivning, ingredienser, instruktioner) från URL:er och konverterar till standardiserad Markdown-format. **Kopplat till:** [`recipe-app`](../recipe-app/) — men kan användas helt oberoende. --- ## Features ### Quick-Import via URL - **Skrapa från ICA.se** — JSON-LD structured data + HTML-parsing - **Generisk parser** — Fallback för andra webbplatser - **Automatisk extraction:** - Receptnamn - Beskrivning (från meta-taggar eller JSON-LD) - Ingredienser med kvantitet, enhet och noter - Instruktioner/tillvägagångssätt - Källlänk (läggs till i footer) ### Stödd format - **Bråkmängder:** `1 1/2 dl`, `1/2 tsk` - **Enheter:** g, kg, ml, dl, msk, tsk, st, port, efter smak, förp, klyfta, m.fl. - **Parentetiska noter:** `2 dl grädde (vispgrädde)` → note sparas separat - **Markdown-output:** Strukturerat receptformat för vidare bearbetning ### Parse-Markdown endpoint Tolka Markdown-format recepter utan databaskomplikationer. Användbar för API-integration utan lokal DB. --- ## Arkitektur ### Backend (NestJS 10.3, TypeScript 5.4.5) **Port:** 3001 ``` src/ ├── app.module.ts # Root module (Quick-import + Recipes) ├── main.ts # Startpunkt ├── common/ │ ├── filters/ │ │ └── global-exception.filter.ts # Centraliserad felhantering (svenska meddelanden) │ └── utils/ │ └── normalize-name.ts # Namnormalisering (åäö-handling) ├── quick-import/ # URL-scraping & parsing │ ├── quick-import.controller.ts # POST /api/quick-import │ ├── quick-import.service.ts # Scraping-logik, parser-selection │ ├── quick-import.module.ts │ └── parsers/ │ ├── base.parser.ts # Abstrakt bas-klass │ ├── ica.parser.ts # ICA.se-specifik (JSON-LD prioritet) │ └── generic.parser.ts # Fallback för alla webbplatser └── recipes/ # Markdown-tolkning (SOM DB!) ├── recipes.controller.ts # POST /api/recipes/parse-markdown ├── recipes.service.ts # Enkel markdown-parsing ├── recipes.module.ts └── dto/ └── parse-markdown.dto.ts # Validering ``` **Viktigt:** Backend har _INGEN_ databaskonfiguration (@prisma/client inte installerat). ### Frontend (Next.js 16.2, React 19.2, TypeScript 5.4.5) **Port:** 3000 ``` app/ ├── layout.tsx # Root layout ├── page.tsx # Home page ├── Navigation.tsx # Minimal nav (Home + Import) ├── import/page.tsx # PRIMARY FEATURE — Import UI │ └── Inmatningsfält för URL/filsökväg │ └── Visa resultat i realtid ├── api/ │ └── parse-markdown-proxy/route.ts # API proxy till backend └── lib/ ├── api.ts # Centraliserad API-access (fetchJson) └── error-handler.ts # parseErrorResponse (svenska meddelanden) ``` --- ## Quick-Start ### Prerequisites - Node.js 22.x - Docker & Docker Compose (valfritt) ### Local Development ```bash # 1. Installera backend cd backend npm install npm run start:dev # Backend kör på http://localhost:3001 # 2. (I ny terminal) Installera frontend cd frontend npm install npm run dev # Frontend kör på http://localhost:3000 ``` Öppna http://localhost:3000 → Gå till `/import` → Klistra in URL ### Docker ```bash # Bygg och starta docker compose up -d # Frontend: http://localhost:3000 # Backend: http://localhost:3001 ``` Stoppa: ```bash docker compose down ``` --- ## API-dokumentation ### POST /api/quick-import **Syfte:** Skrapa webbsida och returnera Markdown-recept **Request:** ```json { "input": "https://ica.se/recept/kottfarssas-1234" } ``` **Response (Success 200):** ```json { "markdown": "# Köttfärssås\n\nEn klassisk...\n\n## Ingredienser\n- 500 g köttfärs\n...", "source": "ica" } ``` **Response (Error 400/503):** ```json { "statusCode": 400, "message": "Kunde inte hämta recept: HTTP 404. Kontrollera att länken är korrekt och försök igen.", "timestamp": "2026-04-12T10:30:00.000Z" } ``` **Error-scenarier:** - `400` — Tomt input, inte en URL, inte en filsökväg - `400` — HTML-parsing misslyckades (receptnamn/ingredienser inte hittade) - `503` — Network-fel (t.ex. webbsidan nåbar, men HTTP 500 från server) --- ### POST /api/recipes/parse-markdown **Syfte:** Tolka Markdown-receptformat utan database **Request:** ```json { "markdown": "# Receptnamn\n\n## Ingredienser\n- 500 g köttfärs\n\n## Tillvägagångssätt\nStek löken..." } ``` **Response (Success 200):** ```json { "name": "Receptnamn", "description": "", "instructions": "Stek löken...", "ingredients": [ { "rawName": "köttfärs", "quantity": 500, "unit": "g", "note": null } ] } ``` --- ## Parser-arkitektur ### Bas-parser (`base.parser.ts`) Abstrakt klass som alla parsers ärver från. Innehåller gemensam parsing-logik: ```typescript abstract class RecipeParser { abstract canHandle(url: string): boolean; abstract parse(html: string): ParsedRecipe; protected parseIngredientLine(line: string): ParsedIngredient | null { // Shared logic för ingrediensparsning } } ``` **parseIngredientLine-funktionen hanterar:** - `500 g köttfärs` → `{quantity: 500, unit: "g", name: "köttfärs"}` - `1 1/2 dl grädde (vispgrädde)` → `{quantity: 1.5, unit: "dl", name: "grädde", note: "vispgrädde"}` - `3 ägg` → `{quantity: 3, unit: "st", name: "ägg"}` - `salt` → `{quantity: 0, unit: "", name: "salt"}` **Kända enheter:** ``` Vikt: g, kg, hg, mg Volym: ml, dl, l, tl Portioner: tsk, msk, krm Övriga: st, port, burk, förp, paket, pris, portion, matsked, tesked, efter smak, klyfta ``` ### ICA Parser (`ica.parser.ts`) Optimerad för ICA.se receptsidor. **Strategi:** 1. Försök extrahera JSON-LD structured data (prioritet) 2. Fallback: HTML-regex parsing **JSON-LD mål:** - Receptnamn från `@type === "Recipe"` → `.name` - Beskrivning från `.description` - Ingredienser från `.recipeIngredient[]` - Instruktioner från `.recipeInstructions[]` **HTML-fallback:** - Titel: `

` eller `` - Beskrivning: `` - Ingredienser: `
  • ` regex - Instruktioner: `
    ` regex ### Generic Parser (`generic.parser.ts`) Fallback-parser för alla okända webbplatser. **Strategi:** 1. Försök JSON-LD structured data (alla webbplatser kan ha detta) 2. Fallback: Permissiv HTML-parsing **HTML-parsing försöker flera selectors:** - Ingredienser: `
  • `, `
    `, `

    ` - Instruktioner: `

    `, `
      ` listor - Titel: `

      `, ``, `` --- ## Markdown-format och parsing ### Input-format ```markdown # Receptnamn Valfri beskrivning av receptet (1+ stycken). ## Ingredienser - 500 g köttfärs - 1 st lök - 2.5 msk tomatpuré - 1 dl grädde (vispgrädde) - salt ## Tillvägagångssätt Steg 1: Stek löken i smör. Steg 2: Tillsätt köttfärsen och stek tills den är genomstekt. ``` ### Parsningsregler | Element | Tolkning | |---------|----------| | `# Rubrik` | Receptnamn (första H1) | | Text mellan H1 och `## Ingredienser` | Beskrivning (flera rader OK, valfritt) | | `## Ingredienser` | Ingredient-markerare (case-insensitive) | | `- ANTAL ENHET NAMN` | Ingrediens med alla delar | | `- ANTAL NAMN` | Ingrediens utan enhet (unit → "st") | | `- NAMN` | Ingrediens utan kvantitet (quantity → 0) | | `(text i parentes)` | Ingrediensnot (sparas separat) | | `## Tillvägagångssätt` / `## Instruktioner` / `## Tillagning` | Instruktions-markerare | | Text under instruktioner | Tillagningssteg (flera rader OK) | **Exempel:** ``` Input: "- 1,5 dl grädde (vispgrädde)" Output: {quantity: 1.5, unit: "dl", rawName: "grädde", note: "vispgrädde"} Input: "- 3 ägg" Output: {quantity: 3, unit: "st", rawName: "ägg", note: null} Input: "- salt" Output: {quantity: 0, unit: "", rawName: "salt", note: null} ``` --- ## Deployment ### Docker Compose (Recommended) ```bash cd microservice-importer docker compose up -d ``` **Services:** - `importer-api` — NestJS backend (port 3001) - `importer-frontend` — Next.js frontend (port 3000) **Stoppa:** ```bash docker compose down ``` **Loggar:** ```bash docker compose logs -f importer-api docker compose logs -f importer-frontend ``` ### Manuell Docker build ```bash # Backend docker build -f backend/Dockerfile -t importer-api:local . docker run -p 3001:3001 importer-api:local # Frontend (ny terminal) docker build -f frontend/Dockerfile -t importer-frontend:local . docker run -p 3000:3000 importer-frontend:local ``` ### Environment variables Konfigureras i `docker compose` eller `.env`: **Backend:** ``` PORT=3001 NODE_ENV=production ``` **Frontend:** ``` NEXT_PUBLIC_API_URL_INTERNAL=http://importer-api:3001 ``` --- ## Integration med Recipe App Recipe App kan anropa denna microservice som extern API: ```typescript const IMPORTER_URL = process.env.MICROSERVICE_IMPORTER_URL || 'http://localhost:3001'; const response = await fetch(`${IMPORTER_URL}/api/quick-import`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: url }) }); const { markdown, source } = await response.json(); // Sedan kan recipe-app:s parse-markdown-endpoint // använda Markdown för DB-matchning mot produkter ``` --- ## Fil-struktur & Koddelning Följande filer är **identiska** mellan recipe-app och microservice-importer: - `backend/src/quick-import/parsers/base.parser.ts` - `backend/src/quick-import/parsers/ica.parser.ts` - `backend/src/quick-import/parsers/generic.parser.ts` - `backend/src/common/filters/global-exception.filter.ts` - `backend/src/common/utils/normalize-name.ts` **Framtida improvement:** Dessa kan förpackas som separat npm-paket för bättre koddelning och versionering. --- ## Tekniska detaljer ### Backend Stack - **NestJS** 10.3 — REST API & modular architecture - **TypeScript** 5.4.5 — Type safety - **Node.js** 22.x — Runtime - **class-validator** — DTO validation (svenska felmeddelanden) - **Ingen databas** — Stateless service ### Frontend Stack - **Next.js** 16.2 — React framework (App Router) - **React** 19.2 — UI components - **TypeScript** 5.4.5 - **Inline CSS** — Minimal styling, no framework dependencies ### Error Handling - Centraliserad `GlobalExceptionFilter` (svenska meddelanden) - Konsistent JSON-responsformat - HTTP status codes: 200, 400, 503 --- ## Framtida utbyggnader - [ ] PDF-import (stubben redan på plats) - [ ] Stöd för fler webbplatser (mat.se, kokaihop.se, etc.) - [ ] Caching av parsed recept - [ ] Rate limiting för scraping - [ ] WebSocket support för real-time parsing - [ ] GraphQL endpoint --- ## Licens Samma som Recipe App — se [main repo](../recipe-app/) --- ## Support Relaterade projekt: - **Recipe App** — [`recipe-app`](../recipe-app/) (Full platform med databas + quick-import integrerad) - **Git Repo** — Gitea på `192.168.50.2:2222/nilsjohan/microservice-importer`