141 lines
4.0 KiB
TypeScript
141 lines
4.0 KiB
TypeScript
import { z } from "zod";
|
|
import { imageTable } from "../db/schema.ts";
|
|
|
|
export const PersonSchema = z.object({
|
|
_type: z.literal("Person"),
|
|
name: z.string().optional(),
|
|
});
|
|
|
|
export const ReviewRatingSchema = z.object({
|
|
bestRating: z.number().optional(),
|
|
worstRating: z.number().optional(),
|
|
// Accept number or string (e.g., "⭐️⭐️⭐️⭐️⭐️")
|
|
ratingValue: z.union([z.number(), z.string()]).optional(),
|
|
});
|
|
|
|
const WithAuthor = z.object({ author: PersonSchema.optional() });
|
|
const WithKeywords = z.object({ keywords: z.array(z.string()).optional() });
|
|
const WithImage = z.object({ image: z.string().optional() });
|
|
const WithDatePublished = z.object({ datePublished: z.string().optional() });
|
|
|
|
const BaseContent = WithAuthor.merge(WithKeywords)
|
|
.merge(WithImage)
|
|
.merge(WithDatePublished);
|
|
|
|
export const BaseFileSchema = z.object({
|
|
type: z.literal("file"),
|
|
name: z.string(),
|
|
path: z.string(),
|
|
modTime: z.string(), // ISO timestamp string
|
|
mime: z.string(),
|
|
size: z.number().int().nonnegative(),
|
|
});
|
|
|
|
const makeContentSchema = <
|
|
TName extends "Article" | "Review" | "Recipe",
|
|
TShape extends z.ZodRawShape,
|
|
>(
|
|
name: TName,
|
|
shape: TShape,
|
|
) =>
|
|
z
|
|
.object({
|
|
_type: z.literal(name),
|
|
keywords: z.array(z.string()).optional(),
|
|
})
|
|
.merge(BaseContent)
|
|
.extend(shape);
|
|
|
|
export const ArticleContentSchema = makeContentSchema("Article", {
|
|
headline: z.string().optional(),
|
|
articleBody: z.string().optional(),
|
|
url: z.string().optional(),
|
|
reviewRating: ReviewRatingSchema.optional(),
|
|
});
|
|
|
|
export const ReviewContentSchema = makeContentSchema("Review", {
|
|
tmdbId: z.number().optional(),
|
|
link: z.string().optional(),
|
|
reviewRating: ReviewRatingSchema.optional(),
|
|
reviewBody: z.string().optional(),
|
|
itemReviewed: z
|
|
.object({
|
|
name: z.string().optional(),
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
export const RecipeContentSchema = makeContentSchema("Recipe", {
|
|
description: z.string().optional(),
|
|
name: z.string().optional(),
|
|
reviewRating: ReviewRatingSchema.optional(),
|
|
recipeIngredient: z.array(z.string()).optional(),
|
|
recipeInstructions: z.array(z.string()).optional(),
|
|
totalTime: z.string().optional(),
|
|
recipeYield: z.number().optional(),
|
|
url: z.string().optional(),
|
|
});
|
|
|
|
export const articleMetadataSchema = z.object({
|
|
headline: z.union([z.null(), z.string()]).describe("Headline of the article"),
|
|
author: z.union([z.null(), z.string()]).describe("Author of the article"),
|
|
datePublished: z.union([z.null(), z.string()]).describe(
|
|
"Date the article was published",
|
|
),
|
|
keywords: z.union([z.null(), z.array(z.string())]).describe(
|
|
"Keywords for the article",
|
|
),
|
|
});
|
|
|
|
export const ArticleSchema = BaseFileSchema.extend({
|
|
content: ArticleContentSchema,
|
|
});
|
|
|
|
export const ReviewSchema = BaseFileSchema.extend({
|
|
content: ReviewContentSchema,
|
|
});
|
|
|
|
export const RecipeSchema = BaseFileSchema.extend({
|
|
content: RecipeContentSchema,
|
|
});
|
|
|
|
export const GenericResourceSchema = z.union([
|
|
ArticleSchema,
|
|
ReviewSchema,
|
|
RecipeSchema,
|
|
]);
|
|
|
|
export type Person = z.infer<typeof PersonSchema>;
|
|
export type ReviewRating = z.infer<typeof ReviewRatingSchema>;
|
|
|
|
export type BaseFile = z.infer<typeof BaseFileSchema>;
|
|
|
|
export type ArticleResource = z.infer<typeof ArticleSchema> & {
|
|
image?: typeof imageTable.$inferSelect;
|
|
};
|
|
|
|
export type ReviewResource = z.infer<typeof ReviewSchema> & {
|
|
image?: typeof imageTable.$inferSelect;
|
|
};
|
|
|
|
export type RecipeResource = z.infer<typeof RecipeSchema> & {
|
|
image?: typeof imageTable.$inferSelect;
|
|
};
|
|
|
|
export type GenericResource = z.infer<typeof GenericResourceSchema> & {
|
|
image?: typeof imageTable.$inferSelect;
|
|
};
|
|
|
|
export function getNameOfResource(res: GenericResource): string {
|
|
if (res.content?._type === "Article" && res.content.headline) {
|
|
return res.content.headline;
|
|
}
|
|
if (res.content?._type === "Review" && res.content.itemReviewed?.name) {
|
|
return res.content.itemReviewed.name;
|
|
}
|
|
if (res.content?._type === "Recipe" && res.content.name) {
|
|
return res.content.name;
|
|
}
|
|
return "Unnamed Resource";
|
|
}
|