9.4 KiB
9.4 KiB
🚀 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
# 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
# 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:
# 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:
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:
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:
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:
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:
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:
DATABASE_URL=mysql://recipe_user:secure_password@recipe-db:3306/recipe_db
IMPORT_SERVICE_URL=http://recipe-import-service:3000
5. Deploy
Lokalt (development):
# 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):
# 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
curl https://recept.gynther.se/api/recipes/import/health
Expected Response:
{
"status": "ok",
"timestamp": "...",
"service": "recipe-import-service",
"version": "1.0.0"
}
Test 2: Import PDF (preview)
curl -X POST \
-F "file=@recipe.pdf" \
https://recept.gynther.se/api/recipes/import/pdf
Test 3: Import & Save
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
# 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
# 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
# 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
- LLM Integration (Mistral) - För avancerad recepttolkning
- Excel/Word support - Expandera filformat
- Caching - Redis för snabbare import
- Error Monitoring - Lägg till Sentry eller liknande
- Rate Limiting - Skydda import-endpoint