559 lines
14 KiB
Markdown
559 lines
14 KiB
Markdown
# 🔌 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
|
|
|
|
---
|