From a1a4f9beb3736584665bf2a782323c8ec52dd40d Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Sun, 12 Apr 2026 17:08:48 +0200 Subject: [PATCH] Update README with comprehensive architecture and API documentation --- README.md | 453 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 423 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 044cdf6..81350e8 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,438 @@ # Microservice Importer -Recipe import microservice för snabb-import av recept från webben. +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 från URL**: Importera recept direkt från ICA.se eller andra webbsidor -- **Automatisk parsing**: Extraherar receptnamn, beskrivning, ingredienser och instruktioner -- **Markdown-format**: Returnerar recept i standardiserad Markdown-format -- **Flersidig parsning**: Stöd för JSON-LD structured data och HTML-parsing +### 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) -- `src/quick-import/` — URL-scraping och parsing -- `src/recipes/` — Markdown-parsing service -- Parsers för site-specifik extraction (ICA, Generic fallback) +### Backend (NestJS 10.3, TypeScript 5.4.5) +**Port:** 3001 -### Frontend (Next.js) -- `app/import/page.tsx` — Import UI -- `app/api/parse-markdown-proxy/` — API proxy till backend - -## Setup - -```bash -# Installera beroenden -cd backend && npm install -cd ../frontend && npm install - -# Kör i development-läge -cd backend && npm run start:dev -cd ../frontend && npm run dev +``` +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 ``` -Backend: http://localhost:3001 -Frontend: http://localhost:3000 +**Viktigt:** Backend har _INGEN_ databaskonfiguration (@prisma/client inte installerat). -## Docker +### Frontend (Next.js 16.2, React 19.2, TypeScript 5.4.5) +**Port:** 3000 -```bash -docker-compose up -d +``` +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) ``` -frontend: http://localhost:3000 -backend: http://localhost:3001 +--- + +## 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`