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

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

  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