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