make api call on the server add tests

This commit is contained in:
Ruidy 2024-06-28 18:35:03 +02:00
parent 277ede1ad3
commit 1539a03084
No known key found for this signature in database
GPG key ID: E00F51288CB857CC
10 changed files with 151 additions and 69 deletions

View file

@ -4,10 +4,10 @@
- [x] use nuxt framework
- [x] rewrite the random page, the current landing page
- [x] rewrite the recipe page
- [ ] deploy
- [ ] nuxt image
- [x] deploy
- [x] nuxt image
- [x] prettier and eslint
- [ ] transition
- [ ] pwa
- [ ] seo, robots.txt
- [ ] update the README
- [x] update the README

BIN
bun.lockb

Binary file not shown.

View file

@ -1,61 +0,0 @@
import type { Recipe } from "~/types/recipe";
type Keyword = "random" | "filter" | "lookup" | "search";
export default async function (keyword: Keyword, param?: string) {
const { data, pending, error } = await useAsyncData(
keyword,
async () => {
const config = useRuntimeConfig();
let url = "";
switch (keyword) {
case "random":
url = `${config.apiUrl}random.php`;
break;
case "filter":
url = "";
break;
case "lookup":
url = `${config.apiUrl}${keyword}.php?i=${param}`;
break;
case "search":
url = "";
break;
default:
throw Error("unexpected URI parameters");
}
return await $fetch(url);
},
{ lazy: true },
);
const tmp = computed(() => data.value?.meals?.[0]);
const names: string[] = [];
const quantities: number[] = [];
for (const [k, v] of Object.entries(tmp.value)) {
if (k.startsWith("strIngredient") && !!v) {
names.push(v);
} else if (k.startsWith("strMeasure") && !!v) {
quantities.push(v);
}
}
const recipe = reactive<Recipe>({
title: tmp.value.strMeal,
pictureUrl: tmp.value.strMealThumb,
videoUrl: tmp.value.strYoutube,
category: tmp.value.strCategory,
origin: tmp.value.strArea,
ingredients: names.map((name, i) => ({ name, quantity: quantities[i] })),
instructions: tmp.value.strInstructions,
});
return {
recipe,
pending,
error,
};
}

View file

@ -16,7 +16,6 @@ export default defineNuxtConfig({
// The private keys which are only available server-side
apiUrl: "",
// Keys within public are also exposed client-side
public: {},
},
ssr: true,
});

View file

@ -1,11 +1,11 @@
<template>
<div class="hero min-h-screen bg-base-200">
<div class="hero min-h-full bg-base-200">
<div class="hero-content flex-col lg:flex-row-reverse">
<NuxtImg src="/chef.svg" class="max-w-sm rounded-lg shadow-2xl" />
<div>
<h1 class="text-5xl font-bold prose">Eat Something New</h1>
<p class="py-6 prose">Generate a random recipe.</p>
<NuxtLink to="/random" class="btn btn-primary" external>
<NuxtLink to="/random" class="btn btn-primary">
Random Recipe Now
</NuxtLink>
</div>

View file

@ -1,5 +1,9 @@
<script setup lang="ts">
const { recipe, pending, error } = await useRecipe("random");
const {
data: recipe,
pending,
error,
} = await useFetch("/api/recipes", { lazy: true });
</script>
<template>

12
server/api/recipes.get.ts Normal file
View file

@ -0,0 +1,12 @@
import { parseRecipeData } from "~/utils/recipes";
export default defineEventHandler(async (_event) => {
const { apiUrl } = useRuntimeConfig();
const data = await $fetch<{ meals: unknown }>(
new URL("random.php", apiUrl).toString(),
);
const recipes = parseRecipeData(data);
return recipes[0];
});

View file

@ -4,6 +4,6 @@ export type Recipe = {
videoUrl: string;
category: string;
origin: string;
ingredients: { name: string; quantity: number }[];
ingredients: { name: string; quantity: string }[];
instructions: string;
};

95
utils/recipes.test.ts Normal file
View file

@ -0,0 +1,95 @@
import { describe, expect, it } from "vitest";
import type { Recipe } from "~/types/recipe";
import { parseRecipeData } from "~/utils/recipes";
const sampleApiResponse = {
meals: [
{
idMeal: "52915",
strMeal: "French Omelette",
strDrinkAlternate: null,
strCategory: "Miscellaneous",
strArea: "French",
strInstructions:
"Get everything ready. Warm a 20cm (measured across the top) non-stick frying pan on a medium heat. Crack the eggs into a bowl and beat them with a fork so they break up and mix, but not as completely as you would for scrambled egg. With the heat on medium-hot, drop one knob of butter into the pan. It should bubble and sizzle, but not brown. Season the eggs with the Parmesan and a little salt and pepper, and pour into the pan.\r\nLet the eggs bubble slightly for a couple of seconds, then take a wooden fork or spatula and gently draw the mixture in from the sides of the pan a few times, so it gathers in folds in the centre. Leave for a few seconds, then stir again to lightly combine uncooked egg with cooked. Leave briefly again, and when partly cooked, stir a bit faster, stopping while theres some barely cooked egg left. With the pan flat on the heat, shake it back and forth a few times to settle the mixture. It should slide easily in the pan and look soft and moist on top. A quick burst of heat will brown the underside.\r\nGrip the handle underneath. Tilt the pan down away from you and let the omelette fall to the edge. Fold the side nearest to you over by a third with your fork, and keep it rolling over, so the omelette tips onto a plate or fold it in half, if thats easier. For a neat finish, cover the omelette with a piece of kitchen paper and plump it up a bit with your fingers. Rub the other knob of butter over to glaze. Serve immediately.",
strMealThumb:
"https://www.themealdb.com/images/media/meals/yvpuuy1511797244.jpg",
strTags: "Egg",
strYoutube: "https://www.youtube.com/watch?v=qXPhVYpQLPA",
strIngredient1: "Eggs",
strIngredient2: "Butter",
strIngredient3: "Parmesan",
strIngredient4: "Tarragon",
strIngredient5: "Parsley",
strIngredient6: "Chives",
strIngredient7: "Gruyère",
strIngredient8: "",
strIngredient9: "",
strIngredient10: "",
strIngredient11: "",
strIngredient12: "",
strIngredient13: "",
strIngredient14: "",
strIngredient15: "",
strIngredient16: "",
strIngredient17: "",
strIngredient18: "",
strIngredient19: "",
strIngredient20: "",
strMeasure1: "3",
strMeasure2: "2 knobs",
strMeasure3: "1 tsp",
strMeasure4: "3 chopped",
strMeasure5: "1 tbs chopped",
strMeasure6: "1 tbs chopped",
strMeasure7: "4 tbs",
strMeasure8: "",
strMeasure9: "",
strMeasure10: "",
strMeasure11: "",
strMeasure12: "",
strMeasure13: "",
strMeasure14: "",
strMeasure15: "",
strMeasure16: "",
strMeasure17: "",
strMeasure18: "",
strMeasure19: "",
strMeasure20: "",
strSource:
"https://www.bbcgoodfood.com/recipes/1669/ultimate-french-omelette",
strImageSource: null,
strCreativeCommonsConfirmed: null,
dateModified: null,
},
],
};
describe("parseRecipeData", () => {
it("should parse the API response into the Recipe type", () => {
const expectedResult: Recipe[] = [
{
title: "French Omelette",
pictureUrl:
"https://www.themealdb.com/images/media/meals/yvpuuy1511797244.jpg",
videoUrl: "https://www.youtube.com/watch?v=qXPhVYpQLPA",
category: "Miscellaneous",
origin: "French",
ingredients: [
{ name: "Eggs", quantity: "3" },
{ name: "Butter", quantity: "2 knobs" },
{ name: "Parmesan", quantity: "1 tsp" },
{ name: "Tarragon", quantity: "3 chopped" },
{ name: "Parsley", quantity: "1 tbs chopped" },
{ name: "Chives", quantity: "1 tbs chopped" },
{ name: "Gruyère", quantity: "4 tbs" },
],
instructions:
"Get everything ready. Warm a 20cm (measured across the top) non-stick frying pan on a medium heat. Crack the eggs into a bowl and beat them with a fork so they break up and mix, but not as completely as you would for scrambled egg. With the heat on medium-hot, drop one knob of butter into the pan. It should bubble and sizzle, but not brown. Season the eggs with the Parmesan and a little salt and pepper, and pour into the pan.\r\nLet the eggs bubble slightly for a couple of seconds, then take a wooden fork or spatula and gently draw the mixture in from the sides of the pan a few times, so it gathers in folds in the centre. Leave for a few seconds, then stir again to lightly combine uncooked egg with cooked. Leave briefly again, and when partly cooked, stir a bit faster, stopping while theres some barely cooked egg left. With the pan flat on the heat, shake it back and forth a few times to settle the mixture. It should slide easily in the pan and look soft and moist on top. A quick burst of heat will brown the underside.\r\nGrip the handle underneath. Tilt the pan down away from you and let the omelette fall to the edge. Fold the side nearest to you over by a third with your fork, and keep it rolling over, so the omelette tips onto a plate or fold it in half, if thats easier. For a neat finish, cover the omelette with a piece of kitchen paper and plump it up a bit with your fingers. Rub the other knob of butter over to glaze. Serve immediately.",
},
];
const result = parseRecipeData(sampleApiResponse);
expect(result).toEqual(expectedResult);
});
});

33
utils/recipes.ts Normal file
View file

@ -0,0 +1,33 @@
import type { Recipe } from "~/types/recipe";
export function parseRecipeData(data: { meals: unknown }): Recipe[] {
return data.meals.map((meal: unknown) => {
// Extract ingredients and measurements
const ingredients: { name: string; quantity: string }[] = [];
for (let i = 1; i <= 20; i++) {
const ingredientName = meal[`strIngredient${i}`];
const ingredientQuantity = meal[`strMeasure${i}`];
if (
ingredientName &&
ingredientName.trim() &&
ingredientQuantity &&
ingredientQuantity.trim()
) {
ingredients.push({
name: ingredientName.trim(),
quantity: ingredientQuantity.trim(),
});
}
}
return {
title: meal.strMeal,
pictureUrl: meal.strMealThumb,
videoUrl: meal.strYoutube,
category: meal.strCategory,
origin: meal.strArea,
ingredients: ingredients,
instructions: meal.strInstructions,
};
});
}