Files
recipe-app/recipe-document-converter/INTEGRATION_GUIDE.md
T
2026-04-11 16:46:48 +02:00

14 KiB

🔌 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
  2. Arkitektur
  3. Backend Integration
  4. Docker Deployment
  5. API Endpoints
  6. Testing
  7. 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

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

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

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:

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:

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:

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:

import { ImportModule } from './modules/import/import.module';

@Module({
  imports: [
    // ... existing modules
    ImportModule,
  ],
})
export class AppModule {}

Docker Deployment

Option 1: Lokal utveckling (utan Docker)

# 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:


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:

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:

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:

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

curl -X POST \
  -F "file=@recipe.pdf" \
  http://localhost:3000/import/pdf

Response:

{
  "success": true,
  "rawText": "...",
  "pdfMetadata": {
    "fileName": "recipe.pdf",
    "pages": 1,
    "author": "Unknown"
  },
  "structuredData": {
    "name": "Kycklingcurry",
    "ingredients": [...],
    "instructions": [...]
  }
}

2. Test recipe-app integration

curl -X POST \
  -F "file=@recipe.pdf" \
  http://localhost:3001/api/recipes/import/pdf

3. Test health checks

# 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)

# 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

# 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

# Reinitialize docker-compose
docker-compose down
docker system prune -a
docker-compose up --build

Filuppladdning misslyckas

# Kontrollera filstorlek (default 50MB)
ls -lh recipe.pdf

# Kontrollera uploads-mapp permissions
docker exec recipe-import-service ls -la /app/uploads/

TypeScript fel

# Uppdatera dependencies
npm install

# Clear cache
npm cache clean --force

# Rebuild
npm run build

Databas-anslutning misslyckad

# 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