feat: refactor whole bunch of stuff
This commit is contained in:
80
lib/marka/index.ts
Normal file
80
lib/marka/index.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { MARKA_API_KEY } from "../env.ts";
|
||||
import { getImage } from "../image.ts";
|
||||
import { GenericResource } from "./schema.ts";
|
||||
|
||||
//const url = `https://marka.max-richter.dev`;
|
||||
const url = "http://localhost:8080";
|
||||
|
||||
async function addImageToResource<T extends GenericResource>(
|
||||
resource: GenericResource,
|
||||
): Promise<T> {
|
||||
const imageUrl = resource?.content?.image;
|
||||
if (imageUrl) {
|
||||
try {
|
||||
const absoluteImageUrl = (imageUrl.startsWith("https://") ||
|
||||
imageUrl.startsWith("http://"))
|
||||
? imageUrl
|
||||
: `${url}/${imageUrl}`;
|
||||
const image = await getImage(absoluteImageUrl);
|
||||
return { ...resource, image } as T;
|
||||
} catch (e) {
|
||||
console.log(`Failed to fetch image: ${imageUrl}`, e);
|
||||
}
|
||||
}
|
||||
return resource as T;
|
||||
}
|
||||
|
||||
export async function fetchResource<T extends GenericResource>(
|
||||
resource: string,
|
||||
): Promise<T | undefined> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${url}/resources/${resource}`,
|
||||
);
|
||||
const res = await response.json();
|
||||
return addImageToResource<T>(res);
|
||||
} catch (_e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export async function listResources<T = GenericResource>(
|
||||
resource: string,
|
||||
): Promise<T[]> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${url}/resources/${resource}`,
|
||||
);
|
||||
const list = await response.json();
|
||||
return Promise.all(
|
||||
list?.content
|
||||
.filter((a: GenericResource) => a?.content?._type)
|
||||
.map((res: GenericResource) => addImageToResource(res)),
|
||||
);
|
||||
} catch (_e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function createResource(
|
||||
path: string,
|
||||
content: string | object | ArrayBuffer,
|
||||
) {
|
||||
const isJson = typeof content === "object" &&
|
||||
!(content instanceof ArrayBuffer);
|
||||
const fetchUrl = `${url}/resources/${path}`;
|
||||
const headers = new Headers();
|
||||
headers.append("Content-Type", isJson ? "application/json" : "");
|
||||
if (MARKA_API_KEY) {
|
||||
headers.append("Authentication", MARKA_API_KEY);
|
||||
}
|
||||
const response = await fetch(fetchUrl, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: isJson ? JSON.stringify(content) : content,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to create resource: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
126
lib/marka/schema.ts
Normal file
126
lib/marka/schema.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
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(),
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user