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

369 lines
9.4 KiB
Markdown

# 🚀 Deployment Guide: Import Service Integration
Din nuvarande setup:
```
recept.gynther.se → Caddy Proxy
├─ recipe-frontend:3000 (Next.js)
├─ recipe-api:8080 (NestJS Backend)
└─ MariaDB
```
Ny setup (med import-service):
```
recept.gynther.se → Caddy Proxy
├─ recipe-frontend:3000 (Next.js)
├─ recipe-api:8080 (NestJS Backend) *← kan anropa import-service*
├─ recipe-import-service:3000 (NestJS Import) *← NYT*
└─ MariaDB
```
---
## 📋 Steg-för-steg deployment
### 1. Uppdatera Caddy configuration
**Option A: Manuell uppdatering**
```bash
# SSH till server
ssh user@server.se
# Backup nuvarande Caddyfile
cp /opt/containers/caddy/conf/Caddyfile /opt/containers/caddy/conf/Caddyfile.backup
# Uppdatera med nya import-service reglerna
# Se Caddyfile.production för den kompletta konfigurationen
```
**Option B: Använda versionerad fil**
```bash
# Kopiera Caddyfile.production till servern
scp Caddyfile.production user@server.se:/opt/containers/caddy/conf/Caddyfile
# Reload Caddy
docker exec caddy-proxy caddy reload
```
---
### 2. Starta import-service container
**Om du använder din nuvarande docker-compose:**
```bash
# SSH till server
ssh user@server.se
# Navigera till import-service mapp
cd /path/to/recipe-document-converter/recipe-document-converter
# Build och starta
docker-compose up -d recipe-import-service
# Eller använd production docker-compose:
docker-compose -f docker-compose.production.yml up -d recipe-import-service
```
**Kontrollera att den körs:**
```bash
docker ps | grep import-service
docker logs recipe-import-service
# Test health endpoint
curl http://localhost:3000/health
```
---
### 3. Integrera import-service i recipe-app backend
Lägg till i ditt `recipe-app/backend` projekt:
**Skapa `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://recipe-import-service:3000';
constructor(private prisma: PrismaService) {}
async importRecipeFromPDF(file: Express.Multer.File) {
try {
if (!file || file.mimetype !== 'application/pdf') {
throw new HttpException('Endast PDF-filer tillåtna', HttpStatus.BAD_REQUEST);
}
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);
}
return { success: true, data: response.data.structuredData, 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, recipe };
} catch (error) {
throw new HttpException('Kunde inte spara recept', HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
```
**Skapa `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 {}
```
**Uppdatera `src/modules/recipes/recipes.controller.ts`:**
```typescript
import { Controller, Post, 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) {}
@Post('import/pdf')
@UseInterceptors(FileInterceptor('file'))
async importRecipeFromPDF(@UploadedFile() file: Express.Multer.File) {
return this.importService.importRecipeFromPDF(file);
}
@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);
}
}
```
**Uppdatera `src/app.module.ts`:**
```typescript
import { ImportModule } from './modules/import/import.module';
@Module({
imports: [
// ... existing modules
ImportModule,
],
})
export class AppModule {}
```
---
### 4. Uppdatera miljövariabler
**`recipe-app/backend/.env` eller docker environment:**
```env
DATABASE_URL=mysql://recipe_user:secure_password@recipe-db:3306/recipe_db
IMPORT_SERVICE_URL=http://recipe-import-service:3000
```
---
### 5. Deploy
**Lokalt (development):**
```bash
# Terminal 1: import-service
cd recipe-document-converter/recipe-document-converter
npm install
npm run start:dev
# Terminal 2: recipe-app backend
cd recipe-app/backend
npm install
IMPORT_SERVICE_URL=http://localhost:3000 npm run start:dev
# Terminal 3: recipe-app frontend
cd recipe-app/frontend
npm run dev
```
**Production (Docker):**
```bash
# Option A: Starta import-service separat
ssh user@server.se
cd /path/to/recipe-document-converter
docker-compose up -d recipe-import-service
# Option B: Använd production docker-compose (all-in-one)
docker-compose -f docker-compose.production.yml up -d
# Reload Caddy
docker exec caddy-proxy caddy reload
```
---
## 🧪 Testing
### Test 1: Import-service health
```bash
curl https://recept.gynther.se/api/recipes/import/health
```
**Expected Response:**
```json
{
"status": "ok",
"timestamp": "...",
"service": "recipe-import-service",
"version": "1.0.0"
}
```
### Test 2: Import PDF (preview)
```bash
curl -X POST \
-F "file=@recipe.pdf" \
https://recept.gynther.se/api/recipes/import/pdf
```
### Test 3: Import & Save
```bash
curl -X POST \
-F "file=@recipe.pdf" \
https://recept.gynther.se/api/recipes/import/pdf/save
```
---
## 📊 Routing Overview
```
https://recept.gynther.se
Caddy Proxy (:443)
├─ / → recipe-frontend:3000
├─ /api/recipes/import/* → recipe-import-service:3000
├─ /api/products* → recipe-api:8080
├─ /api/inventory* → recipe-api:8080
├─ /api/recipes* → recipe-api:8080 (BUT /api/recipes/import/* intercepts here)
└─ /api/* → recipe-frontend:3000
```
**Wichtigt:** Import-endpoints måste komma FÖRE andra /api/recipes* regler för att inte fastna!
---
## 🔍 Troubleshooting
### Import-service inte tillgänglig
```bash
# Kontrollera container
docker ps | grep import-service
# Se loggar
docker logs recipe-import-service
# Kontrollera Caddy routing
docker exec caddy-proxy curl http://recipe-import-service:3000/health
# Reload Caddy
docker exec caddy-proxy caddy reload
```
### 502 Bad Gateway från Caddy
```bash
# Kontrollera att import-service körs
docker logs recipe-import-service
# Kontrollera nätverksanslutning
docker network inspect recipe-network
# Verify environment variable
docker inspect recipe-api | grep IMPORT_SERVICE_URL
```
### Filuppladdning misslyckas
```bash
# Kontrollera volym permissions
docker exec recipe-import-service ls -la /app/uploads/
# Kontrollera filstorlek
ls -lh recipe.pdf
# Öka MAX_FILE_SIZE om behövs (standard: 50MB)
docker exec recipe-import-service env | grep MAX_FILE_SIZE
```
---
## ✅ Deployment Checklist
- [ ] Caddy.production är uppdaterad med import-service reglerna
- [ ] Import-service container är byggd och klar
- [ ] ImportModule är tillagd i recipe-app backend
- [ ] IMPORT_SERVICE_URL är satt i miljövariabler
- [ ] axios är installerat i backend
- [ ] Caddy reloadad
- [ ] Health endpoints testad
- [ ] PDF import testad
- [ ] Loggar monitorade
---
## 🎯 Nästa Steg
1. **LLM Integration (Mistral)** - För avancerad recepttolkning
2. **Excel/Word support** - Expandera filformat
3. **Caching** - Redis för snabbare import
4. **Error Monitoring** - Lägg till Sentry eller liknande
5. **Rate Limiting** - Skydda import-endpoint