Refactor code structure for improved readability and maintainability
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import { ExceptionFilter, ArgumentsHost } from '@nestjs/common';
|
||||
export declare class GlobalExceptionFilter implements ExceptionFilter {
|
||||
private readonly logger;
|
||||
catch(exception: any, host: ArgumentsHost): void;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var GlobalExceptionFilter_1;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GlobalExceptionFilter = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
let GlobalExceptionFilter = GlobalExceptionFilter_1 = class GlobalExceptionFilter {
|
||||
constructor() {
|
||||
this.logger = new common_1.Logger(GlobalExceptionFilter_1.name);
|
||||
}
|
||||
catch(exception, host) {
|
||||
if (!host || host.getType() !== 'http') {
|
||||
this.logger.error(`Non-HTTP exception caught: ${exception?.message ?? exception}`);
|
||||
return;
|
||||
}
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse();
|
||||
const request = ctx.getRequest();
|
||||
const path = request.url;
|
||||
let statusCode = common_1.HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
let message = 'Ett internt serverfel inträffade.';
|
||||
if (exception instanceof common_1.HttpException) {
|
||||
statusCode = exception.getStatus();
|
||||
const exceptionResponse = exception.getResponse();
|
||||
if (typeof exceptionResponse === 'object' && 'message' in exceptionResponse) {
|
||||
message = exceptionResponse.message || message;
|
||||
}
|
||||
else if (typeof exceptionResponse === 'string') {
|
||||
message = exceptionResponse;
|
||||
}
|
||||
}
|
||||
else if (exception instanceof Error) {
|
||||
message = exception.message || message;
|
||||
statusCode = common_1.HttpStatus.BAD_REQUEST;
|
||||
this.logger.error(`Error: ${exception.message}`, exception.stack);
|
||||
}
|
||||
if (message.includes('Unknown unit')) {
|
||||
message = 'Okänd enhet. Kontrollera enheten och försök igen.';
|
||||
}
|
||||
else if (message.includes('Cannot convert between incompatible unit types')) {
|
||||
message = 'Kan inte konvertera mellan dessa enhetstyper.';
|
||||
}
|
||||
else if (message.includes('Inventory item with id') && message.includes('not found')) {
|
||||
message = 'Hemmavarorna hittades inte.';
|
||||
}
|
||||
else if (message.includes('Product with id') && message.includes('not found')) {
|
||||
message = 'Produkten hittades inte.';
|
||||
}
|
||||
else if (message.includes('Recipe with id') && message.includes('not found')) {
|
||||
message = 'Receptet hittades inte.';
|
||||
}
|
||||
else if (message.includes('sourceProductId och targetProductId kan inte vara samma')) {
|
||||
message = 'Du kan inte slå samman en produkt med sig själv.';
|
||||
}
|
||||
const errorResponse = {
|
||||
statusCode,
|
||||
message,
|
||||
timestamp: new Date().toISOString(),
|
||||
path,
|
||||
};
|
||||
this.logger.warn(`${request.method} ${path} - Status: ${statusCode}, Message: ${message}`);
|
||||
response.status(statusCode).json(errorResponse);
|
||||
}
|
||||
};
|
||||
exports.GlobalExceptionFilter = GlobalExceptionFilter;
|
||||
exports.GlobalExceptionFilter = GlobalExceptionFilter = GlobalExceptionFilter_1 = __decorate([
|
||||
(0, common_1.Catch)()
|
||||
], GlobalExceptionFilter);
|
||||
//# sourceMappingURL=global-exception.filter.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"global-exception.filter.js","sourceRoot":"","sources":["../../../src/common/filters/global-exception.filter.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1 @@
|
||||
export declare function downloadAndOptimizeImage(sourceUrl: string, destDir: string): Promise<string>;
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.downloadAndOptimizeImage = downloadAndOptimizeImage;
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const sharp = require("sharp");
|
||||
const uuid_1 = require("uuid");
|
||||
const BLOCKED_HOSTNAMES = /^(localhost|127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|0\.0\.0\.0|::1|fc00:|fe80:)/i;
|
||||
async function downloadAndOptimizeImage(sourceUrl, destDir) {
|
||||
const raw = sourceUrl.trim();
|
||||
const protocolNormalized = raw.startsWith('//') ? `https:${raw}` : raw;
|
||||
let parsedUrl;
|
||||
try {
|
||||
parsedUrl = new URL(protocolNormalized);
|
||||
}
|
||||
catch {
|
||||
throw new Error('Ogiltig bild-URL');
|
||||
}
|
||||
if (parsedUrl.protocol !== 'https:') {
|
||||
throw new Error('Bild-URL måste använda https://');
|
||||
}
|
||||
const hostname = parsedUrl.hostname;
|
||||
if (BLOCKED_HOSTNAMES.test(hostname)) {
|
||||
throw new Error('Bild-URL pekar på ett blockerat nätverk');
|
||||
}
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 10_000);
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(parsedUrl.toString(), {
|
||||
signal: controller.signal,
|
||||
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; RecipeApp/1.0)' },
|
||||
});
|
||||
}
|
||||
finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status} vid nedladdning av bild`);
|
||||
}
|
||||
const contentType = response.headers.get('content-type') ?? '';
|
||||
if (!contentType.startsWith('image/')) {
|
||||
throw new Error(`Ogiltig content-type: ${contentType}`);
|
||||
}
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
if (arrayBuffer.byteLength > 5 * 1024 * 1024) {
|
||||
throw new Error('Bilden är för stor (max 5 MB)');
|
||||
}
|
||||
const imageBuffer = Buffer.from(arrayBuffer);
|
||||
if (!fs.existsSync(destDir)) {
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
}
|
||||
const filename = `${(0, uuid_1.v4)()}.jpg`;
|
||||
const outputPath = path.join(destDir, filename);
|
||||
await sharp(imageBuffer)
|
||||
.resize(1200, 800, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.jpeg({ quality: 80 })
|
||||
.toFile(outputPath);
|
||||
return `/images/${filename}`;
|
||||
}
|
||||
//# sourceMappingURL=download-image.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"download-image.js","sourceRoot":"","sources":["../../../src/common/utils/download-image.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1 @@
|
||||
export declare function normalizeName(value: string): string;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.normalizeName = normalizeName;
|
||||
function normalizeName(value) {
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/[^a-z0-9\s]/g, '')
|
||||
.replace(/\s+/g, '');
|
||||
}
|
||||
//# sourceMappingURL=normalize-name.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"normalize-name.js","sourceRoot":"","sources":["../../../src/common/utils/normalize-name.ts"],"names":[],"mappings":";;AAAA,sCAQC;AARD,SAAgB,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK;SACT,IAAI,EAAE;SACN,WAAW,EAAE;SACb,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;SAC3B,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACzB,CAAC"}
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
@@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const normalize_name_1 = require("./normalize-name");
|
||||
describe('normalizeName', () => {
|
||||
it('gör om till gemener', () => {
|
||||
expect((0, normalize_name_1.normalizeName)('Kycklingfilé')).toBe('kycklingfile');
|
||||
});
|
||||
it('tar bort svenska diakritiska tecken', () => {
|
||||
expect((0, normalize_name_1.normalizeName)('åäö')).toBe('aao');
|
||||
expect((0, normalize_name_1.normalizeName)('ÅÄÖ')).toBe('aao');
|
||||
});
|
||||
it('tar bort mellanslag', () => {
|
||||
expect((0, normalize_name_1.normalizeName)('rökt paprikapulver')).toBe('roktpaprikapulver');
|
||||
});
|
||||
it('tar bort inledande och avslutande mellanslag', () => {
|
||||
expect((0, normalize_name_1.normalizeName)(' lax ')).toBe('lax');
|
||||
});
|
||||
it('tar bort specialtecken', () => {
|
||||
expect((0, normalize_name_1.normalizeName)('Curry (mild)')).toBe('currymild');
|
||||
});
|
||||
it('hanterar siffror', () => {
|
||||
expect((0, normalize_name_1.normalizeName)('Omega-3')).toBe('omega3');
|
||||
});
|
||||
it('hanterar tom sträng', () => {
|
||||
expect((0, normalize_name_1.normalizeName)('')).toBe('');
|
||||
});
|
||||
it('hanterar sträng med bara mellanslag', () => {
|
||||
expect((0, normalize_name_1.normalizeName)(' ')).toBe('');
|
||||
});
|
||||
it('normaliserar accenter korrekt', () => {
|
||||
expect((0, normalize_name_1.normalizeName)('Fläskfilé')).toBe('flaskfile');
|
||||
expect((0, normalize_name_1.normalizeName)('Gräddfil')).toBe('graddfil');
|
||||
expect((0, normalize_name_1.normalizeName)('Rödlök')).toBe('rodlok');
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=normalize-name.spec.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"normalize-name.spec.js","sourceRoot":"","sources":["../../../src/common/utils/normalize-name.spec.ts"],"names":[],"mappings":";;AAAA,qDAAiD;AAEjD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,IAAA,8BAAa,EAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,CAAC,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,IAAA,8BAAa,EAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,IAAA,8BAAa,EAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,CAAC,IAAA,8BAAa,EAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC1B,MAAM,CAAC,IAAA,8BAAa,EAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,IAAA,8BAAa,EAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,IAAA,8BAAa,EAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACrD,MAAM,CAAC,IAAA,8BAAa,EAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACnD,MAAM,CAAC,IAAA,8BAAa,EAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
export interface ParsedIngredient {
|
||||
rawName: string;
|
||||
quantity: number;
|
||||
unit: string;
|
||||
note: string | null;
|
||||
alternatives: string[];
|
||||
}
|
||||
export interface ParsedRecipe {
|
||||
name: string;
|
||||
description: string;
|
||||
instructions: string;
|
||||
ingredients: ParsedIngredient[];
|
||||
}
|
||||
export declare function parseRecipeMarkdown(markdown: string): ParsedRecipe;
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.parseRecipeMarkdown = parseRecipeMarkdown;
|
||||
const KNOWN_UNITS = new Set([
|
||||
'g', 'kg', 'hg', 'mg', 'ml', 'dl', 'l', 'tl',
|
||||
'st', 'tsk', 'msk', 'krm', 'matsled', 'tesled',
|
||||
'pris', 'portion', 'port', 'burk', 'förp', 'paket', 'efter smak', 'klyfta',
|
||||
]);
|
||||
const H1_RE = /^#\s+/;
|
||||
const H2_RE = /^##\s+/;
|
||||
const INGREDIENT_HEADING_RE = /ingrediens/;
|
||||
const INSTRUCTION_HEADING_RE = /instruktion|tillagning|gör så här|steg|tillväg|metod/;
|
||||
const BULLET_RE = /^[-*]\s+/;
|
||||
const PAREN_NOTE_RE = /\(([^)]+)\)\s*$/;
|
||||
const FRACTION_RE = /^(\d+)?\s*(\d+)\s*\/\s*([\d.]+)\s+(\S+)\s+(.+)$/;
|
||||
const RANGE_RE = /^(?:ca\.?\s+)?(\d+(?:[.,]\d+)?)\s*[-–]\s*(?:ca\.?\s+)?(\d+(?:[.,]\d+)?)\s+(\S+)\s+(.+)$/i;
|
||||
const QTY_UNIT_NAME_RE = /^(\d+(?:[.,]\d+)?)\s+(\S+)\s+(.+)$/;
|
||||
const QTY_NAME_RE = /^(\d+(?:[.,]\d+)?)\s+(.+)$/;
|
||||
const ALTERNATIVES_RE = /\s+eller\s+/i;
|
||||
function parseRecipeMarkdown(markdown) {
|
||||
const lines = markdown.split('\n');
|
||||
let name = '';
|
||||
let description = '';
|
||||
let instructions = '';
|
||||
const ingredients = [];
|
||||
let currentSection = 'none';
|
||||
const descriptionLines = [];
|
||||
const instructionLines = [];
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (H1_RE.test(trimmed) && !trimmed.startsWith('##')) {
|
||||
name = trimmed.replace(H1_RE, '').trim();
|
||||
currentSection = 'description';
|
||||
continue;
|
||||
}
|
||||
if (H2_RE.test(trimmed)) {
|
||||
const heading = trimmed.replace(H2_RE, '').trim().toLowerCase();
|
||||
if (INGREDIENT_HEADING_RE.test(heading)) {
|
||||
currentSection = 'ingredients';
|
||||
}
|
||||
else if (INSTRUCTION_HEADING_RE.test(heading)) {
|
||||
currentSection = 'instructions';
|
||||
}
|
||||
else {
|
||||
currentSection = 'none';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
switch (currentSection) {
|
||||
case 'description':
|
||||
if (trimmed.length > 0) {
|
||||
descriptionLines.push(trimmed);
|
||||
}
|
||||
break;
|
||||
case 'ingredients':
|
||||
if (BULLET_RE.test(trimmed)) {
|
||||
const ingredientText = trimmed.replace(BULLET_RE, '');
|
||||
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 };
|
||||
}
|
||||
function parseIngredientLine(text) {
|
||||
const trimmed = text.trim();
|
||||
const toAlternatives = (rawName) => ALTERNATIVES_RE.test(rawName)
|
||||
? rawName.split(ALTERNATIVES_RE).map((s) => s.trim()).filter(Boolean)
|
||||
: [rawName];
|
||||
let note = null;
|
||||
let main = trimmed;
|
||||
const parenMatch = trimmed.match(PAREN_NOTE_RE);
|
||||
if (parenMatch) {
|
||||
note = parenMatch[1].trim();
|
||||
main = trimmed.slice(0, parenMatch.index).trim();
|
||||
}
|
||||
const fractionMatch = main.match(FRACTION_RE);
|
||||
if (fractionMatch) {
|
||||
const whole = fractionMatch[1] ? parseFloat(fractionMatch[1]) : 0;
|
||||
const quantity = whole + parseFloat(fractionMatch[2]) / parseFloat(fractionMatch[3]);
|
||||
const candidateUnit = fractionMatch[4].toLowerCase();
|
||||
if (KNOWN_UNITS.has(candidateUnit)) {
|
||||
const rawName = fractionMatch[5].trim();
|
||||
return { quantity, unit: candidateUnit, rawName, note, alternatives: toAlternatives(rawName) };
|
||||
}
|
||||
}
|
||||
const rangeMatch = main.match(RANGE_RE);
|
||||
if (rangeMatch) {
|
||||
const candidateUnit = rangeMatch[3].toLowerCase();
|
||||
if (KNOWN_UNITS.has(candidateUnit)) {
|
||||
const lo = parseNumber(rangeMatch[1]);
|
||||
const hi = parseNumber(rangeMatch[2]);
|
||||
const rawName = rangeMatch[4].trim();
|
||||
return { quantity: (lo + hi) / 2, unit: candidateUnit, rawName, note, alternatives: toAlternatives(rawName) };
|
||||
}
|
||||
}
|
||||
const fullMatch = main.match(QTY_UNIT_NAME_RE);
|
||||
if (fullMatch) {
|
||||
const candidateUnit = fullMatch[2].toLowerCase();
|
||||
if (KNOWN_UNITS.has(candidateUnit)) {
|
||||
const rawName = fullMatch[3].trim();
|
||||
return { quantity: parseNumber(fullMatch[1]), unit: candidateUnit, rawName, note, alternatives: toAlternatives(rawName) };
|
||||
}
|
||||
const rawName = fullMatch[2] + ' ' + fullMatch[3];
|
||||
return { quantity: parseNumber(fullMatch[1]), unit: 'st', rawName, note, alternatives: toAlternatives(rawName) };
|
||||
}
|
||||
const noUnitMatch = main.match(QTY_NAME_RE);
|
||||
if (noUnitMatch) {
|
||||
const rawName = noUnitMatch[2].trim();
|
||||
return { quantity: parseNumber(noUnitMatch[1]), unit: 'st', rawName, note, alternatives: toAlternatives(rawName) };
|
||||
}
|
||||
return { quantity: 0, unit: '', rawName: main, note, alternatives: toAlternatives(main) };
|
||||
}
|
||||
function parseNumber(s) {
|
||||
return parseFloat(s.replace(',', '.'));
|
||||
}
|
||||
//# sourceMappingURL=recipe-parser.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const recipe_parser_1 = require("./recipe-parser");
|
||||
describe('parseRecipeMarkdown — ingrediensformat', () => {
|
||||
const parse = (line) => {
|
||||
const md = `# Test\n## Ingredienser\n- ${line}\n## Instruktioner\n1. Gör det.`;
|
||||
return (0, recipe_parser_1.parseRecipeMarkdown)(md).ingredients[0];
|
||||
};
|
||||
it('vanlig rad: kvantitet enhet namn', () => {
|
||||
const ing = parse('400 g kycklingfilé');
|
||||
expect(ing).toMatchObject({ quantity: 400, unit: 'g', rawName: 'kycklingfilé' });
|
||||
});
|
||||
it('bråk: 1 1/2 dl grädde', () => {
|
||||
const ing = parse('1 1/2 dl grädde');
|
||||
expect(ing.quantity).toBeCloseTo(1.5);
|
||||
expect(ing).toMatchObject({ unit: 'dl', rawName: 'grädde' });
|
||||
});
|
||||
it('bråk utan heltal: 1/2 dl mjölk', () => {
|
||||
const ing = parse('1/2 dl mjölk');
|
||||
expect(ing.quantity).toBeCloseTo(0.5);
|
||||
expect(ing).toMatchObject({ unit: 'dl', rawName: 'mjölk' });
|
||||
});
|
||||
it('intervall: 600 - ca 700 g kycklingfilé', () => {
|
||||
const ing = parse('600 - ca 700 g kycklingfilé');
|
||||
expect(ing.quantity).toBeCloseTo(650);
|
||||
expect(ing).toMatchObject({ unit: 'g', rawName: 'kycklingfilé' });
|
||||
});
|
||||
it('intervall: ca 600-700 g kycklingfilé', () => {
|
||||
const ing = parse('ca 600-700 g kycklingfilé');
|
||||
expect(ing.quantity).toBeCloseTo(650);
|
||||
expect(ing).toMatchObject({ unit: 'g' });
|
||||
});
|
||||
it('intervall med alternativt namn: 600 - ca 700 g kycklingfilé eller kycklinginnerfilé', () => {
|
||||
const ing = parse('600 - ca 700 g kycklingfilé eller kycklinginnerfilé');
|
||||
expect(ing.quantity).toBeCloseTo(650);
|
||||
expect(ing).toMatchObject({ unit: 'g', rawName: 'kycklingfilé eller kycklinginnerfilé' });
|
||||
});
|
||||
it('parentes-not: 2 dl grädde (eller crème fraiche)', () => {
|
||||
const ing = parse('2 dl grädde (eller crème fraiche)');
|
||||
expect(ing).toMatchObject({ quantity: 2, unit: 'dl', rawName: 'grädde', note: 'eller crème fraiche' });
|
||||
});
|
||||
it('utan enhet: 3 ägg', () => {
|
||||
const ing = parse('3 ägg');
|
||||
expect(ing).toMatchObject({ quantity: 3, unit: 'st', rawName: 'ägg' });
|
||||
});
|
||||
it('bara namn: salt', () => {
|
||||
const ing = parse('salt');
|
||||
expect(ing).toMatchObject({ quantity: 0, unit: '', rawName: 'salt' });
|
||||
});
|
||||
it('decimalkomma: 2,5 dl mjölk', () => {
|
||||
const ing = parse('2,5 dl mjölk');
|
||||
expect(ing.quantity).toBeCloseTo(2.5);
|
||||
expect(ing).toMatchObject({ unit: 'dl', rawName: 'mjölk' });
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=recipe-parser.spec.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"recipe-parser.spec.js","sourceRoot":"","sources":["../../../src/common/utils/recipe-parser.spec.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
export type UnitType = 'weight' | 'volume' | 'cooking' | 'piece' | 'other';
|
||||
export interface UnitDefinition {
|
||||
value: string;
|
||||
labelSv: string;
|
||||
type: UnitType;
|
||||
toBaseFactor: number;
|
||||
aliases: string[];
|
||||
}
|
||||
export declare const UNIT_DEFINITIONS: UnitDefinition[];
|
||||
export declare function normalizeUnit(unit: string): string;
|
||||
export declare function getUnitDefinition(unit: string): UnitDefinition | undefined;
|
||||
export declare function getUnitType(unit: string): UnitType | null;
|
||||
export declare function canConvert(fromUnit: string, toUnit: string): boolean;
|
||||
export declare function convertUnit(quantity: number, fromUnit: string, toUnit: string): number;
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UNIT_DEFINITIONS = void 0;
|
||||
exports.normalizeUnit = normalizeUnit;
|
||||
exports.getUnitDefinition = getUnitDefinition;
|
||||
exports.getUnitType = getUnitType;
|
||||
exports.canConvert = canConvert;
|
||||
exports.convertUnit = convertUnit;
|
||||
exports.UNIT_DEFINITIONS = [
|
||||
{ value: 'g', labelSv: 'g (gram)', type: 'weight', toBaseFactor: 1, aliases: ['gram'] },
|
||||
{ value: 'hg', labelSv: 'hg (hektogram)', type: 'weight', toBaseFactor: 100, aliases: ['hektogram'] },
|
||||
{ value: 'kg', labelSv: 'kg (kilogram)', type: 'weight', toBaseFactor: 1000, aliases: ['kilo', 'kilogram'] },
|
||||
{ value: 'mg', labelSv: 'mg (milligram)', type: 'weight', toBaseFactor: 0.001, aliases: ['milligram'] },
|
||||
{ value: 'ml', labelSv: 'ml (milliliter)', type: 'volume', toBaseFactor: 1, aliases: ['milliliter'] },
|
||||
{ value: 'cl', labelSv: 'cl (centiliter)', type: 'volume', toBaseFactor: 10, aliases: ['centiliter'] },
|
||||
{ value: 'dl', labelSv: 'dl (deciliter)', type: 'volume', toBaseFactor: 100, aliases: ['deciliter'] },
|
||||
{ value: 'l', labelSv: 'l (liter)', type: 'volume', toBaseFactor: 1000, aliases: ['liter'] },
|
||||
{ value: 'krm', labelSv: 'krm (kryddmått)', type: 'cooking', toBaseFactor: 1, aliases: ['kryddmatt', 'kryddmått'] },
|
||||
{ value: 'tsk', labelSv: 'tsk (tesked)', type: 'cooking', toBaseFactor: 5, aliases: ['tesked', 'test'] },
|
||||
{ value: 'msk', labelSv: 'msk (matsked)', type: 'cooking', toBaseFactor: 15, aliases: ['matsked', 'matsled'] },
|
||||
{ value: 'st', labelSv: 'st (styck)', type: 'piece', toBaseFactor: 1, aliases: ['stycke', 'styck', 'stk'] },
|
||||
{ value: 'port', labelSv: 'port (portioner)', type: 'piece', toBaseFactor: 1, aliases: ['portion', 'portioner'] },
|
||||
{ value: 'förp', labelSv: 'förp (förpackning)', type: 'piece', toBaseFactor: 1, aliases: ['forp', 'förpackning', 'forpackning'] },
|
||||
{ value: 'klyfta', labelSv: 'klyfta', type: 'piece', toBaseFactor: 1, aliases: [] },
|
||||
{ value: 'efter smak', labelSv: 'efter smak', type: 'other', toBaseFactor: 1, aliases: ['eftr smak', 'efter smak'] },
|
||||
];
|
||||
const _unitLookup = new Map();
|
||||
for (const def of exports.UNIT_DEFINITIONS) {
|
||||
_unitLookup.set(def.value.toLowerCase(), def);
|
||||
for (const alias of def.aliases) {
|
||||
_unitLookup.set(alias.toLowerCase(), def);
|
||||
}
|
||||
}
|
||||
function normalizeUnit(unit) {
|
||||
const key = unit.trim().toLowerCase();
|
||||
return _unitLookup.get(key)?.value ?? key;
|
||||
}
|
||||
function getUnitDefinition(unit) {
|
||||
const key = unit.trim().toLowerCase();
|
||||
return _unitLookup.get(key);
|
||||
}
|
||||
function getUnitType(unit) {
|
||||
return getUnitDefinition(unit)?.type ?? null;
|
||||
}
|
||||
function canConvert(fromUnit, toUnit) {
|
||||
const from = getUnitDefinition(fromUnit);
|
||||
const to = getUnitDefinition(toUnit);
|
||||
if (!from || !to)
|
||||
return false;
|
||||
if (from.type !== to.type)
|
||||
return false;
|
||||
if (from.type === 'piece' || from.type === 'other')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
function convertUnit(quantity, fromUnit, toUnit) {
|
||||
if (quantity <= 0) {
|
||||
throw new Error(`Invalid quantity: ${quantity}. Quantity must be positive.`);
|
||||
}
|
||||
const normalizedFrom = normalizeUnit(fromUnit);
|
||||
const normalizedTo = normalizeUnit(toUnit);
|
||||
if (normalizedFrom === normalizedTo)
|
||||
return quantity;
|
||||
const fromDef = getUnitDefinition(normalizedFrom);
|
||||
const toDef = getUnitDefinition(normalizedTo);
|
||||
if (!fromDef)
|
||||
throw new Error(`Unknown unit: "${fromUnit}"`);
|
||||
if (!toDef)
|
||||
throw new Error(`Unknown unit: "${toUnit}"`);
|
||||
if (fromDef.type !== toDef.type) {
|
||||
throw new Error(`Cannot convert between incompatible unit types: "${fromUnit}" (${fromDef.type}) and "${toUnit}" (${toDef.type})`);
|
||||
}
|
||||
if (fromDef.type === 'piece' || fromDef.type === 'other') {
|
||||
return quantity;
|
||||
}
|
||||
return (quantity * fromDef.toBaseFactor) / toDef.toBaseFactor;
|
||||
}
|
||||
//# sourceMappingURL=units.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"units.js","sourceRoot":"","sources":["../../../src/common/utils/units.ts"],"names":[],"mappings":"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"}
|
||||
Reference in New Issue
Block a user