# πŸ”Œ Integration Guide: Recipe Import Service Denna guide visar hur du integrerar **recipe-document-converter** (import-service) med din befintliga **recipe-app**. ## πŸ“‹ InnehΓ₯llsfΓΆrteckning 1. [Installation & Setup](#installation--setup) 2. [Arkitektur](#arkitektur) 3. [Backend Integration](#backend-integration) 4. [Docker Deployment](#docker-deployment) 5. [API Endpoints](#api-endpoints) 6. [Testing](#testing) 7. [Troubleshooting](#troubleshooting) --- ## Installation & Setup ### 1. Krav - **recipe-app** β€” Din befintliga receptapp (Next.js frontend + NestJS backend) - **recipe-document-converter** β€” Import-service (denna repo) - **Docker** 24+ och **Docker Compose** - **Node.js** 22.x ### 2. Klona och organisera projekten ```bash dev/ β”œβ”€β”€ recipe-app/ # Din befintliga app β”‚ β”œβ”€β”€ frontend/ β”‚ β”œβ”€β”€ backend/ β”‚ β”œβ”€β”€ Dockerfile β”‚ └── compose.yml β”‚ └── recipe-document-converter/ # Import-service β”œβ”€β”€ recipe-document-converter/ β”œβ”€β”€ Dockerfile β”œβ”€β”€ docker-compose.yml β”œβ”€β”€ Caddyfile └── README.md ``` ### 3. Uppdatera recipe-document-converter paket ```bash cd recipe-document-converter/recipe-document-converter # TypeScript Γ€r redan uppdaterad till 5.4.5 βœ… # Installera dependencies npm install ``` --- ## Arkitektur ### System Diagram ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Caddy Reverse Proxy β”‚ β”‚ (port 80/443) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Frontend β”‚ β”‚ Backend API β”‚ β”‚ Import β”‚ β”‚ :4000 β”‚ β”‚ :3001 β”‚ β”‚ :3000 β”‚ β”‚ β”‚ β”‚ (NestJS) β”‚ β”‚ (NestJS) β”‚ β”‚ Next.js β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ 16.2 β”‚ β”‚ Recipes ┐ β”‚ β”‚ PDF β”‚ β”‚ β”‚ β”‚ Inventory β”œβ”€β”€β”€β”€β”€β”Όβ”€β”€extract β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Products β”‚ β”‚ β”‚ structure β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ MariaDB 11 β”‚ β”‚ β”‚ (recipe_db) β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β” β”‚ Uploads/ β”‚ β”‚ (Volym) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Data Flow ``` 1. AnvΓ€ndare laddar upp PDF ↓ 2. recipe-app frontend β†’ backend POST /recipes/import/pdf ↓ 3. Backend β†’ FormData β†’ import-service POST /import/pdf ↓ 4. import-service extraherar β†’ JSON (ingredients, instructions, metadata) ↓ 5. Backend validerar + sparar i Prisma/MariaDB ↓ 6. Frontend visar importerat recept ``` --- ## Backend Integration ### Steg 1: Uppdatera recipe-app backend ```bash cd recipe-app/backend # Installera axios om det saknas npm install axios # Om du anvΓ€nder FormData npm install form-data ``` ### Steg 2: Skapa import-modul Skapa `backend/src/modules/import/import.service.ts`: ```typescript import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import axios from 'axios'; import FormData from 'form-data'; import * as fs from 'fs'; import { PrismaService } from '../../prisma/prisma.service'; interface RecipeImportDTO { name: string; ingredients: Array<{ name: string; quantity?: number; unit?: string; }>; instructions: string[]; metadata?: { prepTime?: string; servings?: number; }; } @Injectable() export class ImportService { private readonly IMPORT_SERVICE_URL = process.env.IMPORT_SERVICE_URL || 'http://import-service:3000'; constructor(private prisma: PrismaService) {} async importRecipeFromPDF(file: Express.Multer.File) { try { if (!file) { throw new HttpException('Ingen fil angiven', HttpStatus.BAD_REQUEST); } if (file.mimetype !== 'application/pdf') { throw new HttpException('Endast PDF-filer tillΓ₯tna', HttpStatus.BAD_REQUEST); } // Call import-service const form = new FormData(); form.append('file', fs.createReadStream(file.path)); const response = await axios.post(`${this.IMPORT_SERVICE_URL}/import/pdf`, form, { headers: form.getHeaders(), timeout: 30000, }); if (!response.data.success) { throw new HttpException(response.data.error, HttpStatus.BAD_REQUEST); } const recipeData = this.mapImportDataToRecipe(response.data.structuredData); return { success: true, data: recipeData, metadata: response.data.pdfMetadata, }; } catch (error) { if (file?.path && fs.existsSync(file.path)) { fs.unlinkSync(file.path); } if (error instanceof axios.AxiosError) { throw new HttpException( `Import misslyckades: ${error.message}`, HttpStatus.SERVICE_UNAVAILABLE, ); } throw error; } } async saveImportedRecipe(recipeData: RecipeImportDTO) { try { const recipe = await this.prisma.recipe.create({ data: { name: recipeData.name, instructions: recipeData.instructions.join('\n'), prepTime: recipeData.metadata?.prepTime, servings: recipeData.metadata?.servings, ingredients: { create: recipeData.ingredients.map((ing) => ({ name: ing.name, quantity: ing.quantity, unit: ing.unit, })), }, }, include: { ingredients: true }, }); return { success: true, message: 'Recept sparat', recipe }; } catch (error) { throw new HttpException( `Kunde inte spara recept: ${error instanceof Error ? error.message : 'OkΓ€nt fel'}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } private mapImportDataToRecipe(importedData: any): RecipeImportDTO { return { name: importedData.name || 'Importerat recept', ingredients: importedData.ingredients || [], instructions: importedData.instructions || [], metadata: importedData.metadata || {}, }; } } ``` ### Steg 3: Skapa import-modul Skapa `backend/src/modules/import/import.module.ts`: ```typescript import { Module } from '@nestjs/common'; import { ImportService } from './import.service'; @Module({ providers: [ImportService], exports: [ImportService], }) export class ImportModule {} ``` ### Steg 4: LΓ€gg till endpoints i recipes controller Uppdatera `backend/src/modules/recipes/recipes.controller.ts`: ```typescript import { Controller, Post, Get, UseInterceptors, UploadedFile } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { ImportService } from '../import/import.service'; @Controller('recipes') export class RecipesController { constructor(private importService: ImportService) {} /** * Import recipe from PDF without saving * POST /recipes/import/pdf */ @Post('import/pdf') @UseInterceptors(FileInterceptor('file')) async importRecipeFromPDF(@UploadedFile() file: Express.Multer.File) { return this.importService.importRecipeFromPDF(file); } /** * Import and save recipe * POST /recipes/import/pdf/save */ @Post('import/pdf/save') @UseInterceptors(FileInterceptor('file')) async importAndSaveRecipe(@UploadedFile() file: Express.Multer.File) { const imported = await this.importService.importRecipeFromPDF(file); return this.importService.saveImportedRecipe(imported.data); } /** * Check import service health * GET /recipes/import/health */ @Get('import/health') async importServiceHealth() { return this.importService.checkImportServiceHealth(); } } ``` ### Steg 5: Registrera import-modul Uppdatera `backend/src/app.module.ts`: ```typescript import { ImportModule } from './modules/import/import.module'; @Module({ imports: [ // ... existing modules ImportModule, ], }) export class AppModule {} ``` --- ## Docker Deployment ### Option 1: Lokal utveckling (utan Docker) ```bash # Terminal 1: Start import-service cd recipe-document-converter/recipe-document-converter npm install npm run start:dev # Terminal 2: Start recipe-app backend cd recipe-app/backend npm install IMPORT_SERVICE_URL=http://localhost:3000 npm run start:dev # Terminal 3: Start recipe-app frontend cd recipe-app/frontend npm install npm run dev ``` **Access:** - Frontend: http://localhost:3000 (Next.js default) - Backend: http://localhost:3001/api - Import: http://localhost:3000/import --- ### Option 2: Docker Compose (Full Stack) **FΓΆrutsΓ€ttningar:** - `recipe-app/` ligger bredvid `recipe-document-converter/` - recipe-app har `Dockerfile` i root **Starta all-in-one:** ```bash cd recipe-document-converter/recipe-document-converter # Bygg och starta alla tjΓ€nster docker-compose up -d # Eller med bygge docker-compose up -d --build ``` **TjΓ€nster:** ``` - Frontend: http://localhost (via Caddy) - Backend API: http://localhost/api - Import Service: http://localhost/import - Health: http://localhost/health ``` **Se loggar:** ```bash docker-compose logs -f recipe-app-backend docker-compose logs -f import-service docker-compose logs -f recipe-db docker-compose logs -f caddy ``` **Stoppa allt:** ```bash docker-compose down ``` --- ## API Endpoints ### Import Service Endpoints | Method | Endpoint | Beskrivning | Auth | |--------|----------|-------------|------| | `GET` | `/health` | HΓ€lsokontroll | Nej | | `POST` | `/import/pdf` | Importera PDF | Nej | ### Recipe App Backend Integration Endpoints | Method | Endpoint | Beskrivning | |--------|----------|-------------| | `POST` | `/api/recipes/import/pdf` | Importera PDF (preview) | | `POST` | `/api/recipes/import/pdf/save` | Importera och spara | | `GET` | `/api/recipes/import/health` | Health check | --- ## Testing ### 1. Test import-service isolerat ```bash curl -X POST \ -F "file=@recipe.pdf" \ http://localhost:3000/import/pdf ``` **Response:** ```json { "success": true, "rawText": "...", "pdfMetadata": { "fileName": "recipe.pdf", "pages": 1, "author": "Unknown" }, "structuredData": { "name": "Kycklingcurry", "ingredients": [...], "instructions": [...] } } ``` ### 2. Test recipe-app integration ```bash curl -X POST \ -F "file=@recipe.pdf" \ http://localhost:3001/api/recipes/import/pdf ``` ### 3. Test health checks ```bash # Import service curl http://localhost:3000/health # Recipe app backend curl http://localhost:3001/health # Import from backend curl http://localhost:3001/api/recipes/import/health ``` ### 4. Test full end-to-end (Docker) ```bash # Start services docker-compose up -d # Wait for services to be healthy sleep 10 # Check all services curl http://localhost/health curl http://localhost/api/health curl http://localhost/import/health # Import recipe curl -X POST \ -F "file=@recipe.pdf" \ http://localhost/api/recipes/import/pdf/save ``` --- ## Troubleshooting ### Import Service Γ€r inte tillgΓ€nglig ```bash # Kontrollera om den kΓΆrs docker ps | grep import-service # Se loggar docker logs recipe-import-service # Kontrollera hΓ€lsa curl http://localhost:3000/health ``` ### Docker nΓ€tverk-error ```bash # Reinitialize docker-compose docker-compose down docker system prune -a docker-compose up --build ``` ### Filuppladdning misslyckas ```bash # Kontrollera filstorlek (default 50MB) ls -lh recipe.pdf # Kontrollera uploads-mapp permissions docker exec recipe-import-service ls -la /app/uploads/ ``` ### TypeScript fel ```bash # Uppdatera dependencies npm install # Clear cache npm cache clean --force # Rebuild npm run build ``` ### Databas-anslutning misslyckad ```bash # Kontrollera MariaDB docker logs recipe-db # Kontrollera URL echo $DATABASE_URL # Test direkten docker exec recipe-db mysql -urecipe_user -psecure_password -e "USE recipe_db; SHOW TABLES;" ``` --- ## Production Checklist - [ ] TypeScript compilerar utan fel - [ ] Alla miljΓΆvariabler Γ€r satta - [ ] Database-backup Γ€r konfigurerat - [ ] Import-service Γ€r tillgΓ€nglig frΓ₯n backend - [ ] Caddy certifikater Γ€r satta upp - [ ] Log rotation Γ€r konfigurerat - [ ] Health endpoints fungerar - [ ] File uploads permissions Γ€r rΓ€tt - [ ] Rate limiting Γ€r konfigurerat (if needed) - [ ] Monitoring setup Γ€r pΓ₯ plats --- ## NΓ€sta steg 1. **LLM Integration** β€” LΓ€gg till Mistral fΓΆr avancerad strukturering 2. **Excel/Word support** β€” Expandera till fler filformat 3. **OCR** β€” LΓ€gg till stΓΆd fΓΆr skannade dokument 4. **Caching** β€” Implementera Redis fΓΆr snabbare import 5. **Authentication** β€” LΓ€gg till auth om behΓΆvs ---