369 lines
9.4 KiB
Markdown
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
|