feat: Add Zod validation for categories API response

This commit is contained in:
Ruidy (aider) 2024-12-14 15:28:20 +01:00 committed by Ruidy
parent 296b2048e9
commit 2010270bcf
No known key found for this signature in database
GPG key ID: E00F51288CB857CC
5 changed files with 64 additions and 39 deletions

View file

@ -1,30 +1,28 @@
<script setup lang="ts">
import type { Category } from "~/types/category";
defineProps<{
category: Category;
}>();
</script>
<template>
<NuxtLink
:to="`/category/${category.strCategory}`"
<nuxt-link
:to="`/category/${category.name}`"
class="block max-w-sm rounded-lg border border-gray-200 bg-white p-6 shadow hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700"
>
<img
:src="category.strCategoryThumb"
:alt="category.strCategory"
:src="category.picture"
:alt="category.name"
class="mb-4 h-48 w-full object-cover rounded"
/>
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
{{ category.strCategory }}
<h5
class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white"
>
{{ category.name }}
</h5>
<p class="font-normal text-gray-700 dark:text-gray-400">
{{ category.strCategoryDescription }}
{{ category.description }}
</p>
</NuxtLink>
</nuxt-link>
</template>
<script setup lang="ts">
interface Category {
strCategory: string
strCategoryThumb: string
strCategoryDescription: string
}
defineProps<{
category: Category
}>()
</script>

View file

@ -1,6 +1,8 @@
<script setup lang="ts">
const { data: categories, status, error } = useCategories();
console.log(categories.value);
if (error.value) {
throw createError({
statusCode: 500,
@ -15,25 +17,26 @@ if (error.value) {
<span class="loading loading-spinner loading-lg text-primary" />
</div>
<div
v-else-if="categories?.length > 0"
v-else-if="categories!.length > 0"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 my-8"
>
<div
v-for="category in categories"
:key="category.strCategory"
:key="category.name"
class="card bg-base-100 shadow-xl h-[28rem] sm:h-[32rem] md:h-[36rem] lg:h-[32rem]"
>
<figure>
<img :src="category.strCategoryThumb" :alt="category.strCategory" />
<img :src="category.picture" :alt="category.name" />
</figure>
<div class="card-body">
<h2 class="card-title">{{ category.strCategory }}</h2>
<p class="line-clamp-6 text-sm">{{ category.strCategoryDescription }}</p>
<h2 class="card-title">{{ category.name }}</h2>
<p class="line-clamp-6 text-sm">
{{ category.description }}
</p>
<div class="card-actions justify-end">
<nuxt-link
:to="`/category/${category.strCategory}`"
:to="`/category/${category.name}`"
class="btn btn-primary"
>
View Recipes

View file

@ -1,27 +1,28 @@
import { publicProcedure, router } from "../trpc";
import {} from "~/types/recipe";
import {
categoriesResponseSchema,
type CategoriesResponse,
} from "~/types/category";
const { apiUrl } = useRuntimeConfig();
type Category = {
idCategory: string;
strCategory: string;
strCategoryThumb: string;
strCategoryDescription: string;
};
export const categoryRouter = router({
listCategories: publicProcedure.query(async () => {
const data = await $fetch<{ categories: Category[] }>(
new URL("categories.php", apiUrl).toString(),
const response = await $fetch<CategoriesResponse>(
new URL("categories.php", apiUrl).href,
);
if (!data?.categories) {
const result = categoriesResponseSchema.safeParse(response);
if (!result.success) {
throw createError({
statusCode: 500,
statusMessage: "Failed to fetch categories",
statusMessage: "Invalid API response format",
data: result.error,
});
}
return data.categories;
return result.data.categories.sort((a, b) => a.name.localeCompare(b.name));
}),
});

22
types/category.ts Normal file
View file

@ -0,0 +1,22 @@
import { z } from "zod";
const categorySchema = z
.object({
idCategory: z.string(),
strCategory: z.string(),
strCategoryThumb: z.string().url(),
strCategoryDescription: z.string(),
})
.transform((c) => ({
identity: c.idCategory,
name: c.strCategory,
picture: c.strCategoryThumb,
description: c.strCategoryDescription,
}));
export const categoriesResponseSchema = z.object({
categories: z.array(categorySchema),
});
export type Category = z.infer<typeof categorySchema>;
export type CategoriesResponse = z.infer<typeof categoriesResponseSchema>;

View file

@ -72,4 +72,5 @@ export const apiResponseSchema = z.object({
});
export type Meal = z.infer<typeof mealSchema>;
export type ApiResponse = z.infer<typeof apiResponseSchema>;