169 lines
4.9 KiB
TypeScript
169 lines
4.9 KiB
TypeScript
/**
|
|
* INTEGRATION GUIDE: RECIPE IMPORT SERVICE
|
|
*
|
|
* This file shows how to integrate recipe-document-converter with recipe-app backend.
|
|
* Copy and adapt this code to your recipe-app/backend directory.
|
|
*
|
|
* Path: backend/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'; // Adjust path based on your project
|
|
|
|
interface RecipeImportDTO {
|
|
name: string;
|
|
ingredients: Array<{
|
|
name: string;
|
|
quantity?: number;
|
|
unit?: string;
|
|
}>;
|
|
instructions: string[];
|
|
metadata?: {
|
|
prepTime?: string;
|
|
servings?: number;
|
|
author?: string;
|
|
};
|
|
}
|
|
|
|
@Injectable()
|
|
export class ImportService {
|
|
private readonly IMPORT_SERVICE_URL = process.env.IMPORT_SERVICE_URL || 'http://import-service:3000';
|
|
|
|
constructor(private prisma: PrismaService) {}
|
|
|
|
/**
|
|
* Upload a PDF file to the import service and extract recipe data
|
|
*
|
|
* Usage in your recipe controller:
|
|
* @Post('import/pdf')
|
|
* @UseInterceptors(FileInterceptor('file'))
|
|
* async importRecipeFromPDF(@UploadedFile() file: Express.Multer.File) {
|
|
* return this.importService.importRecipeFromPDF(file);
|
|
* }
|
|
*/
|
|
async importRecipeFromPDF(file: Express.Multer.File) {
|
|
try {
|
|
// Validate file
|
|
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 || 'Receptextrahering misslyckades', HttpStatus.BAD_REQUEST);
|
|
}
|
|
|
|
// Extract and validate recipe data
|
|
const recipeData = this.mapImportDataToRecipe(response.data.structuredData);
|
|
|
|
return {
|
|
success: true,
|
|
data: recipeData,
|
|
metadata: response.data.pdfMetadata,
|
|
};
|
|
} catch (error) {
|
|
// Clean up uploaded file
|
|
if (file && file.path && fs.existsSync(file.path)) {
|
|
fs.unlinkSync(file.path);
|
|
}
|
|
|
|
if (error instanceof axios.AxiosError) {
|
|
throw new HttpException(
|
|
`Receptextrahering misslyckades: ${error.message}`,
|
|
HttpStatus.SERVICE_UNAVAILABLE,
|
|
);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save imported recipe to database
|
|
*
|
|
* Usage in your recipe controller:
|
|
* @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);
|
|
* }
|
|
*/
|
|
async saveImportedRecipe(recipeData: RecipeImportDTO) {
|
|
try {
|
|
// Create recipe with ingredients
|
|
const recipe = await this.prisma.recipe.create({
|
|
data: {
|
|
name: recipeData.name,
|
|
instructions: recipeData.instructions.join('\n'),
|
|
prepTime: recipeData.metadata?.prepTime,
|
|
servings: recipeData.metadata?.servings,
|
|
author: recipeData.metadata?.author,
|
|
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,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map import service data to recipe format
|
|
*/
|
|
private mapImportDataToRecipe(importedData: any): RecipeImportDTO {
|
|
return {
|
|
name: importedData.name || 'Importerat recept',
|
|
ingredients: importedData.ingredients || [],
|
|
instructions: importedData.instructions || [],
|
|
metadata: importedData.metadata || {},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Health check for import service (useful for monitoring)
|
|
*/
|
|
async checkImportServiceHealth() {
|
|
try {
|
|
const response = await axios.get(`${this.IMPORT_SERVICE_URL}/health`, {
|
|
timeout: 5000,
|
|
});
|
|
return response.data;
|
|
} catch {
|
|
throw new HttpException('Import-service är ej tillgänglig', HttpStatus.SERVICE_UNAVAILABLE);
|
|
}
|
|
}
|
|
}
|