import { Injectable, BadRequestException } from '@nestjs/common'; import { IcaRecipeParser } from './parsers/ica.parser'; import { GenericRecipeParser } from './parsers/generic.parser'; import { PdfRecipeParser } from './parsers/pdf.parser'; import { RecipeParser } from './parsers/base.parser'; export interface QuickImportResult { markdown: string; source: 'ica' | 'pdf' | 'other'; } @Injectable() export class QuickImportService { /** * Detekterar typ av input (URL eller fil) och importerar från lämplig källa */ async importFromInput(input: string, fileBuffer?: Buffer): Promise { input = input.trim(); console.log('[QuickImport] Mottog input:', input); if (!input) { throw new BadRequestException('Du måste ange en URL eller ladda upp en fil'); } // Detektera typ const isUrl = this.isUrl(input); const isPdf = this.isPdfPath(input); console.log('[QuickImport] isUrl:', isUrl, 'isPdf:', isPdf); if (isUrl) { console.log('[QuickImport] Detekterade URL, försöker scrapa...'); return this.scrapeRecipeFromUrl(input); } else if (isPdf) { console.log('[QuickImport] Detekterade PDF-fil'); if (!fileBuffer) { throw new BadRequestException('PDF-fil kräver filinnehåll (fileBuffer)'); } return this.parsePdfFile(fileBuffer); } else { console.log('[QuickImport] Input är inte URL eller PDF'); throw new BadRequestException( 'Ogültig input. Ange en gyltig URL (t.ex. ica.se/recept/...) eller ladda upp en PDF-fil' ); } } /** * Kontrollerar om input är en URL */ private isUrl(input: string): boolean { try { new URL(input); return true; } catch { return false; } } /** * Kontrollerar om input är en PDF-filsökväg */ private isPdfPath(input: string): boolean { const normalized = input.toLowerCase(); return normalized.endsWith('.pdf'); } /** * Skrapar recept från en URL * * Använder site-specifika parsers om tillgängliga, * annars fallback till generisk parser. * * @param url URL till receptsidan * @returns Markdown-format */ private async scrapeRecipeFromUrl(url: string): Promise { try { console.log('[QuickImport] Hämtar HTML från:', url); // Hämta HTML från URL const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', }, }); console.log('[QuickImport] HTTP status:', response.status); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const html = await response.text(); console.log('[QuickImport] HTML längd:', html.length, 'tecken'); // Välj lämplig parser const parsers: RecipeParser[] = [ new IcaRecipeParser(), new GenericRecipeParser(), ]; let recipe = null; for (const parser of parsers) { if (parser.canHandle(url)) { console.log('[QuickImport] Använder parser:', parser.constructor.name); recipe = parser.parse(html); break; } } if (!recipe) { throw new Error('Ingen parserutrustning tillgänglig'); } console.log('[QuickImport] Parsad recept:', { name: recipe.name, ingredienser: recipe.ingredients.length, }); if (!recipe.name) { throw new Error('Kunde inte hitta receptnamn på sidan. Försök med en annan länk.'); } // Konvertera till Markdown-format const markdown = this.recipeToMarkdown(recipe, url); console.log('[QuickImport] Markdown genererad, längd:', markdown.length); // Detektera källa från URL let source: 'ica' | 'pdf' | 'other' = 'other'; if (/ica\.se/i.test(url)) { source = 'ica'; } return { markdown, source, }; } catch (err) { const message = err instanceof Error ? err.message : 'Okänt fel vid scraping'; console.error('[QuickImport] ERROR:', message); throw new BadRequestException( `Kunde inte hämta recept: ${message}. Kontrollera att länken är korrekt och försök igen.` ); } } /** * Parsar PDF-fil och konverterar till Markdown */ private async parsePdfFile(fileBuffer: Buffer): Promise { try { console.log('[QuickImport] Parsar PDF-fil...'); const pdfParser = new PdfRecipeParser(); const recipe = await pdfParser.parse(fileBuffer); console.log('[QuickImport] PDF parsad:', { name: recipe.name, ingredienser: recipe.ingredients.length, }); if (!recipe.name) { throw new Error('Kunde inte hitta receptnamn i PDF-filen.'); } // Konvertera till Markdown-format const markdown = this.recipeToMarkdown(recipe); console.log('[QuickImport] Markdown genererad från PDF, längd:', markdown.length); return { markdown, source: 'pdf', }; } catch (err) { const message = err instanceof Error ? err.message : 'Okänt fel vid PDF-parsing'; console.error('[QuickImport] PDF ERROR:', message); throw new BadRequestException( `Kunde inte läsa PDF-filen: ${message}. Kontrollera att det är ett giltigt recept i PDF-format.` ); } } /** * Konvertera receptobjekt till Markdown-format */ private recipeToMarkdown( recipe: { name: string; description?: string; ingredients: Array<{ quantity: number; unit: string; name: string; note?: string; }>; instructions?: string; }, sourceUrl?: string, ): string { const lines: string[] = []; // Titel lines.push(`# ${recipe.name}`); lines.push(''); // Beskrivning if (recipe.description) { lines.push(recipe.description); lines.push(''); } // Ingredienser if (recipe.ingredients.length > 0) { lines.push('## Ingredienser'); for (const ing of recipe.ingredients) { const quantity = ing.quantity > 0 ? `${ing.quantity} ` : ''; const unit = ing.unit ? `${ing.unit} ` : ''; const note = ing.note ? ` (${ing.note})` : ''; lines.push(`- ${quantity}${unit}${ing.name}${note}`); } lines.push(''); } // Instruktioner if (recipe.instructions) { lines.push('## Tillvägagångssätt'); lines.push(recipe.instructions); lines.push(''); } // Källa if (sourceUrl) { lines.push('---'); lines.push(''); lines.push(`Källa: [${sourceUrl}](${sourceUrl})`); } return lines.join('\n'); } }