From f84ee39197a0cdbc0cec62fa781e4f5e179e3260 Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Sun, 12 Apr 2026 08:15:55 +0200 Subject: [PATCH] refactor: Simplify Dockerfile by removing recipe-document-converter build stage and update package.json to remove its dependency --- backend/Dockerfile | 21 +--- backend/package.json | 1 - backend/src/recipes/recipes.service.ts | 153 ++++++++++++++++++++++++- 3 files changed, 155 insertions(+), 20 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index c269a984..157eb00e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,21 +1,9 @@ # Byggas från projektets rot: docker build -f backend/Dockerfile -t recipe-api:local . -# Stage 1: Bygg recipe-document-converter -FROM node:22-alpine AS converter-build -WORKDIR /converter -COPY recipe-document-converter/package.json ./ -RUN npm install -COPY recipe-document-converter/src ./src -COPY recipe-document-converter/tsconfig.json ./ -RUN npm run build - -# Stage 2: Bygg applikationen +# Stage 1: Bygg applikationen FROM node:22-alpine AS builder WORKDIR /app -# Kopiera BYGGT recipe-document-converter (med dist/) -COPY --from=converter-build /converter /recipe-document-converter - # Kopiera backend-filer COPY backend/package.json ./ COPY backend/prisma ./prisma @@ -23,16 +11,13 @@ COPY backend/src ./src COPY backend/tsconfig.json ./ COPY backend/nest-cli.json ./ -# Köra npm install - det kommer att se /recipe-document-converter med dist/ redan byggt +# Köra npm install RUN npm install RUN npx prisma generate RUN npm run build -RUN npx prisma generate -RUN npm run build - -# Stage 4: Kör applikationen +# Stage 2: Kör applikationen FROM node:22-alpine AS runner WORKDIR /app ENV NODE_ENV=production diff --git a/backend/package.json b/backend/package.json index 651b87f5..b7ac7a32 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,7 +11,6 @@ "prisma:deploy": "prisma migrate deploy" }, "dependencies": { - "recipe-document-converter": "file:../recipe-document-converter", "@nestjs/common": "^10.3.0", "@nestjs/core": "^10.3.0", "@nestjs/platform-express": "^10.3.0", diff --git a/backend/src/recipes/recipes.service.ts b/backend/src/recipes/recipes.service.ts index 35e18af3..05168035 100644 --- a/backend/src/recipes/recipes.service.ts +++ b/backend/src/recipes/recipes.service.ts @@ -3,7 +3,21 @@ import { Prisma } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; import { CreateRecipeDto } from './dto/create-recipe.dto'; import { ParseMarkdownDto } from './dto/parse-markdown.dto'; -import { parseRecipeMarkdown, ParsedIngredient } from 'recipe-document-converter'; + +// Lokala typdefiniitioner (tidigare från recipe-document-converter) +interface ParsedIngredient { + rawName: string; + quantity: number; + unit: string; + note: string | null; +} + +interface ParsedRecipe { + name: string; + description: string; + instructions: string; + ingredients: ParsedIngredient[]; +} @Injectable() export class RecipesService { @@ -467,4 +481,141 @@ export class RecipesService { ingredients: ingredientsWithSuggestions, }; } +} + +// ============================================================================ +// Parser Functions (previously from recipe-document-converter library) +// ============================================================================ + +/** + * Parsar ett recept i Markdown-format och extraherar namn, beskrivning, + * instruktioner och ingredienser. + * + * Förväntat format: + * # Receptnamn + * Beskrivning (valfritt stycke efter titeln) + * + * ## Ingredienser + * - 400 g kycklingfilé + * - 2 dl grädde (eller crème fraiche) + * + * ## Instruktioner + * 1. Stek kycklingen … + */ +function parseRecipeMarkdown(markdown: string): ParsedRecipe { + const lines = markdown.split('\n'); + + let name = ''; + let description = ''; + let instructions = ''; + const ingredients: ParsedIngredient[] = []; + + let currentSection: 'none' | 'description' | 'ingredients' | 'instructions' = 'none'; + const descriptionLines: string[] = []; + const instructionLines: string[] = []; + + for (const line of lines) { + const trimmed = line.trim(); + + // H1 — receptnamn + if (/^#\s+/.test(trimmed) && !trimmed.startsWith('##')) { + name = trimmed.replace(/^#\s+/, '').trim(); + currentSection = 'description'; + continue; + } + + // H2 — sektionsrubriker + if (/^##\s+/.test(trimmed)) { + const heading = trimmed.replace(/^##\s+/, '').trim().toLowerCase(); + if (/ingrediens/.test(heading)) { + currentSection = 'ingredients'; + } else if (/instruktion|tillagning|gör så här|steg/.test(heading)) { + currentSection = 'instructions'; + } else { + currentSection = 'none'; + } + continue; + } + + // Samla rader beroende på sektion + switch (currentSection) { + case 'description': + if (trimmed.length > 0) { + descriptionLines.push(trimmed); + } + break; + + case 'ingredients': + if (/^[-*]\s+/.test(trimmed)) { + const ingredientText = trimmed.replace(/^[-*]\s+/, ''); + ingredients.push(parseIngredientLine(ingredientText)); + } + break; + + case 'instructions': + if (trimmed.length > 0) { + instructionLines.push(trimmed); + } + break; + } + } + + description = descriptionLines.join('\n'); + instructions = instructionLines.join('\n'); + + return { name, description, instructions, ingredients }; +} + +/** + * Parsar en ingrediensrad, t.ex.: + * "400 g kycklingfilé" + * "2 dl grädde (eller crème fraiche)" + * "1 kruka basilika" + * "salt" + */ +function parseIngredientLine(text: string): ParsedIngredient { + const trimmed = text.trim(); + + // Extrahera eventuell parentes-not i slutet + let note: string | null = null; + let main = trimmed; + const parenMatch = trimmed.match(/\(([^)]+)\)\s*$/); + if (parenMatch) { + note = parenMatch[1].trim(); + main = trimmed.slice(0, parenMatch.index).trim(); + } + + // Försök matcha "kvantitet enhet namn" — t.ex. "400 g kycklingfilé" eller "2.5 dl grädde" + const fullMatch = main.match(/^(\d+(?:[.,]\d+)?)\s+(\S+)\s+(.+)$/); + if (fullMatch) { + return { + quantity: parseNumber(fullMatch[1]), + unit: fullMatch[2], + rawName: fullMatch[3].trim(), + note, + }; + } + + // Försök matcha "kvantitet namn" utan enhet — t.ex. "3 ägg" + const noUnitMatch = main.match(/^(\d+(?:[.,]\d+)?)\s+(.+)$/); + if (noUnitMatch) { + return { + quantity: parseNumber(noUnitMatch[1]), + unit: 'st', + rawName: noUnitMatch[2].trim(), + note, + }; + } + + // Bara ett namn, ingen kvantitet — t.ex. "salt" + return { + quantity: 0, + unit: '', + rawName: main, + note, + }; +} + +function parseNumber(s: string): number { + return parseFloat(s.replace(',', '.')); } \ No newline at end of file