Convert submodule to regular directory

This commit is contained in:
Nils-Johan Gynther
2026-04-11 16:46:48 +02:00
parent 343416a28d
commit 4189f94e0e
13 changed files with 1781 additions and 1 deletions
@@ -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
---