Convert submodule to regular directory
This commit is contained in:
@@ -0,0 +1,558 @@
|
||||
# 🔌 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
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user