From 4a241c1cb9d3df09e553234285f51903ddafa53d Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Tue, 14 Apr 2026 22:28:37 +0200 Subject: [PATCH] refactor: Remove PDF parser and quick import controller/service implementations --- .../src/quick-import/parsers/pdf.parser.ts | 116 --------- .../quick-import.controller.new.ts | 25 -- .../quick-import/quick-import.service.new.ts | 242 ------------------ 3 files changed, 383 deletions(-) delete mode 100644 backend/src/quick-import/parsers/pdf.parser.ts delete mode 100644 backend/src/quick-import/quick-import.controller.new.ts delete mode 100644 backend/src/quick-import/quick-import.service.new.ts diff --git a/backend/src/quick-import/parsers/pdf.parser.ts b/backend/src/quick-import/parsers/pdf.parser.ts deleted file mode 100644 index 8c390f05..00000000 --- a/backend/src/quick-import/parsers/pdf.parser.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Parser för PDF-filer - * Använder pdf-parse för att extrahera text från PDF-dokument - */ -import { RecipeParser, ParsedRecipe } from './base.parser'; -import * as pdf from 'pdf-parse'; - -export class PdfRecipeParser extends RecipeParser { - canHandle(url: string): boolean { - // Denna parser hanterar PDF-filer - const normalized = url.toLowerCase(); - return normalized.endsWith('.pdf'); - } - - async parse(fileBuffer: Buffer): Promise { - console.log('[PdfParser] Parsing PDF file...'); - - try { - // Extrahera text från PDF - const data = await pdf(fileBuffer); - const text = data.text; - console.log('[PdfParser] Extraherad text längd:', text.length); - - // Parsa texten till receptstruktur - return this.parseRecipeText(text); - } catch (err) { - console.error('[PdfParser] Fel vid PDF-parsing:', err); - throw new Error('Kunde inte tolka PDF-filen. Kontrollera att det är ett giltigt recept.'); - } - } - - /** - * Parsar råtext från PDF till strukturerat recept - * Försöker identifiera receptnamn, ingredienser och instruktioner - */ - private parseRecipeText(text: string): ParsedRecipe { - const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0); - - let name = 'Okänt recept'; - let description = ''; - const ingredients: Array<{ quantity: number; unit: string; name: string; note?: string }> = []; - let instructions = ''; - let currentSection: 'name' | 'description' | 'ingredients' | 'instructions' | null = null; - - // Försök hitta receptnamn (stor text i början) - const titleMatch = text.match(/^[A-ZÅÄÖ\s]+/i); - if (titleMatch) { - name = titleMatch[0].trim(); - } - - // Analysera texten rad för rad - for (const line of lines) { - // Hoppa över tomma rader - if (!line || line.length === 0) continue; - - // Detektera sektioner - if (line.toLowerCase().includes('ingredienser')) { - currentSection = 'ingredients'; - continue; - } - - if (line.toLowerCase().includes('tillvägagångssätt') || - line.toLowerCase().includes('instruktioner') || - line.toLowerCase().includes('gör så här')) { - currentSection = 'instructions'; - continue; - } - - // Samla in innehåll baserat på aktuell sektion - switch (currentSection) { - case 'ingredients': - if (line.toLowerCase().includes('tillvägagångssätt') || - line.toLowerCase().includes('instruktioner')) { - currentSection = 'instructions'; - break; - } - - // Parsa ingrediensrad - const ingredient = this.parseIngredientLine(line); - if (ingredient) { - ingredients.push(ingredient); - } - break; - - case 'instructions': - if (instructions.length > 0) { - instructions += '\n'; - } - instructions += line; - break; - - default: - // Om vi inte har hittat ingredienser än, kan detta vara beskrivning - if (ingredients.length === 0 && !description.includes(line)) { - if (description.length > 0) { - description += ' '; - } - description += line; - } - break; - } - } - - // Om vi inte hittade något receptnamn, försök använda första meningsfulla raden - if (name === 'Okänt recept' && lines.length > 0) { - name = lines[0].length > 50 ? lines[0].substring(0, 50) + '...' : lines[0]; - } - - return { - name, - description: description || undefined, - ingredients, - instructions: instructions || undefined, - }; - } -} \ No newline at end of file diff --git a/backend/src/quick-import/quick-import.controller.new.ts b/backend/src/quick-import/quick-import.controller.new.ts deleted file mode 100644 index ba4db57f..00000000 --- a/backend/src/quick-import/quick-import.controller.new.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Controller, Post, Body, UseInterceptors, UploadedFile } from '@nestjs/common'; -import { FileInterceptor } from '@nestjs/platform-express'; -import { QuickImportService, QuickImportResult } from './quick-import.service'; - -@Controller('quick-import') -export class QuickImportController { - constructor(private readonly quickImportService: QuickImportService) {} - - @Post() - @UseInterceptors(FileInterceptor('file')) - async importFromInput( - @Body() body: { input: string }, - @UploadedFile() file?: Express.Multer.File - ): Promise { - // Om en fil laddats upp, använd filen - if (file) { - console.log('[QuickImportController] Mottog fil:', file.originalname); - return this.quickImportService.importFromInput(file.originalname, file.buffer); - } - - // Annars använd text-input (URL) - console.log('[QuickImportController] Mottog text-input:', body.input); - return this.quickImportService.importFromInput(body.input); - } -} \ No newline at end of file diff --git a/backend/src/quick-import/quick-import.service.new.ts b/backend/src/quick-import/quick-import.service.new.ts deleted file mode 100644 index 99f2c44d..00000000 --- a/backend/src/quick-import/quick-import.service.new.ts +++ /dev/null @@ -1,242 +0,0 @@ -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'); - } -} \ No newline at end of file