services.ts (view raw)
1import type { DatabaseSync } from "node:sqlite";
2import { callLLM } from "./utils/llm.ts";
3import { decodeHtmlChars } from "./utils/html.ts";
4
5export const getRecipesList = (db: DatabaseSync, ids?: number[]): Recipe[] => {
6 if (ids)
7 return db
8 .prepare(
9 `SELECT * FROM recipes WHERE id IN (${ids
10 .map(() => "?")
11 .join(",")}) ORDER BY name`
12 )
13 .all(...ids);
14 else return db.prepare("SELECT * FROM recipes ORDER BY name").all();
15};
16
17export const storeRecipe = async (db: DatabaseSync, recipeId: string) => {
18 let recipeUrl = "";
19 if (!recipeId) throw new Error("Please provide a recipe ID");
20 else if (recipeId.startsWith("https://")) recipeUrl = recipeId;
21 else recipeUrl = `https://cookidoo.fr/recipes/recipe/fr-FR/${recipeId}`;
22
23 const recipePage = await fetch(recipeUrl).then((res) => res.text());
24 const jsonLdTag = recipePage.match(
25 /<script type="application\/ld\+json">(.*?)<\/script>/s
26 )?.[1];
27 if (!jsonLdTag) throw new Error("No JSON-LD tag found");
28
29 const recipeJson = JSON.parse(jsonLdTag);
30
31 const existingItem = db
32 .prepare("SELECT * FROM recipes WHERE url = ?")
33 .get(recipeUrl);
34 if (existingItem) {
35 console.warn("Recipe already exists in db");
36 return null;
37 }
38
39 const rawIngredientsJson = await callLLM(`
40 À partir de la liste suivante, renvoi un objet JSON avec unit, quantity, name pour chacun des éléments.
41 ${decodeHtmlChars(recipeJson.recipeIngredient.join("\n"))}
42 Si la quantité est un nombre décimal, convertis le.
43 Utilise de préférences les unités les plus courantes comme g, pincée, cuillère à café, gousse.
44 Les unités doivent toujours être au singulier.
45 `);
46
47 const recipe = {
48 name: recipeJson.name,
49 ingredients: rawIngredientsJson,
50 imageUrl: recipeJson.image,
51 url: recipeUrl,
52 };
53
54 const storedRecipe = db
55 .prepare(
56 `INSERT INTO recipes (url, name, ingredients, imageUrl) VALUES (?, ?, ?, ?)`
57 )
58 .run(recipe.url, recipe.name, recipe.ingredients, recipe.imageUrl);
59
60 console.log(`New recipe "${recipe.name}" added to db.`);
61
62 return storedRecipe;
63};
64
65export const getGroceryList = (
66 db: DatabaseSync,
67 recipeIds: number[]
68): GroceryItem[] => {
69 const recipes = db
70 .prepare(`SELECT * FROM recipes WHERE id IN (${recipeIds.join(",")})`)
71 .all();
72
73 const rawIngredients = recipes.flatMap((recipe) =>
74 JSON.parse(recipe.ingredients as string)
75 );
76
77 return rawIngredients.reduce((acc, ingredient) => {
78 const existingIngredient = acc.find(
79 (i) => i.name === ingredient.name && i.unit === ingredient.unit
80 );
81 if (existingIngredient) existingIngredient.value += ingredient.value;
82 else acc.push(ingredient);
83 return acc;
84 }, [] as { name: string; value: number; unit: string }[]);
85};