refactor: move types into seperate file, add zodFetch

This commit is contained in:
DuroCodes
2024-05-08 13:37:10 -04:00
parent 6ff3328990
commit c5a80836be
7 changed files with 145 additions and 71 deletions

View File

@@ -1,17 +1,8 @@
---
import PluginModal from "./PluginModal.astro";
import { Markdown } from "@astropub/md";
import PluginModal from "./PluginModal.astro";
import DeprecatedIcon from "./DeprecatedIcon.astro";
export interface Plugin {
description: string;
hash: string;
name: string;
author: string[];
link: string;
example: string;
version: string;
}
import type { Plugin } from "../utils/types";
type Props = Plugin;

View File

@@ -1,7 +1,7 @@
---
import type { Plugin } from "./PluginCard.astro";
import { Code } from "@astrojs/starlight/components";
import { Markdown } from "@astropub/md";
import type { Plugin } from "../utils/types";
import Modal from "./Modal.astro";
type Props = Plugin;

View File

@@ -1,31 +1,7 @@
---
export interface Sponsor {
id: string;
name: string;
roles: string[];
isAdmin: boolean;
isCore: boolean;
isBacker: boolean;
since: string;
image: string;
description: string | null;
collectiveSlug: string;
totalAmountDonated: number;
type: string;
publicMessage: string | null;
isIncognito: boolean;
__typename: string;
}
import type { Contributor } from "../utils/types";
type Props = Pick<
Sponsor,
| "name"
| "image"
| "totalAmountDonated"
| "isAdmin"
| "publicMessage"
| "collectiveSlug"
>;
type Props = Contributor;
const {
name,

View File

@@ -1,13 +1,22 @@
---
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
import { CardGrid } from "@astrojs/starlight/components";
import PluginCard, { type Plugin } from "../components/PluginCard.astro";
import { z } from "astro/zod";
import PluginCard from "../components/PluginCard.astro";
import { PluginSchema } from "../utils/types";
import { zodFetch } from "../utils/fetch";
const plugins = (await (
await fetch(
"https://raw.githubusercontent.com/sern-handler/awesome-plugins/main/pluginlist.json",
)
).json()) as Plugin[];
const pluginResponse = await zodFetch(
z.array(PluginSchema),
"Failed to fetch plugins",
"https://raw.githubusercontent.com/sern-handler/awesome-plugins/main/pluginlist.json",
);
if (!pluginResponse.ok) {
return console.error(pluginResponse.error);
}
const plugins = pluginResponse.value;
---
<StarlightPage frontmatter={{ title: "Plugins", template: "splash" }}>

View File

@@ -1,36 +1,41 @@
---
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
import type { Sponsor } from "../components/SponsorCard.astro";
import { z } from "astro/zod";
import SponsorCard from "../components/SponsorCard.astro";
import { zodFetch } from "../utils/fetch";
import { OpenCollectiveAccountSchema } from "../utils/types";
interface SponsorResponse {
data: {
account: {
contributors: {
nodes: Sponsor[];
};
};
};
const sponsorResponse = await zodFetch(
z.object({
data: z.object({
account: OpenCollectiveAccountSchema,
}),
}),
"Failed to fetch sponsors",
"https://opencollective.com/api/graphql/v2",
{
body: JSON.stringify({
operationName: "BannerTopContributors",
variables: {
collectiveSlug: "sern",
},
query:
"query BannerTopContributors($collectiveSlug: String!) {\n account(slug: $collectiveSlug, throwIfMissing: false) {\n id\n currency\n slug\n ... on AccountWithContributions {\n contributors(limit: 150) {\n totalCount\n nodes {\n id\n name\n roles\n isAdmin\n isCore\n isBacker\n since\n image\n description\n collectiveSlug\n totalAmountDonated\n type\n publicMessage\n isIncognito\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}",
}),
method: "POST",
headers: {
"content-type": "application/json",
},
},
);
if (!sponsorResponse.ok) {
return console.error(sponsorResponse.error);
}
const sponsors = (
(await (
await fetch("https://opencollective.com/api/graphql/v2", {
body: JSON.stringify({
operationName: "BannerTopContributors",
variables: {
collectiveSlug: "sern",
},
query:
"query BannerTopContributors($collectiveSlug: String!) {\n account(slug: $collectiveSlug, throwIfMissing: false) {\n id\n currency\n slug\n ... on AccountWithContributions {\n contributors(limit: 150) {\n totalCount\n nodes {\n id\n name\n roles\n isAdmin\n isCore\n isBacker\n since\n image\n description\n collectiveSlug\n totalAmountDonated\n type\n publicMessage\n isIncognito\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}",
}),
method: "POST",
headers: {
"content-type": "application/json",
},
})
).json()) as SponsorResponse
).data.account.contributors.nodes.filter((s) => s.totalAmountDonated > 0);
const sponsors = sponsorResponse.value.data.account.contributors.nodes.filter(
(s) => s.totalAmountDonated > 0,
);
---
<StarlightPage frontmatter={{ title: "Sponsors", template: "splash" }}>

40
src/utils/fetch.ts Normal file
View File

@@ -0,0 +1,40 @@
import type { z } from "astro/zod";
import { Ok, Err } from "./types";
/**
* @param schema The Zod schema to validate the response against
* @param error An error message to return if the response is invalid (e.g. "Failed to fetch data")
* @param args The arguments to pass to fetch (e.g. URL, headers, etc.)
* @returns A Result type containing either the parsed JSON or an error message (specified by the error parameter)
*
* @example
* const plugins = await zodFetch(PluginSchema, "Failed to fetch plugins", "/api/plugins");
* if (!plugins.ok) {
* console.error(plugins.error); // "Failed to fetch plugins"
* }
* console.log(plugins.value); // { description: "A plugin", hash: "123", name: "My Plugin", author: ["Me"], link: "https://example.com", example: "example" }
*/
export const zodFetch = async <TSchema extends z.Schema>(
schema: TSchema,
error: string,
...args: Parameters<typeof fetch>
) => {
const res = await fetch(...args);
if (!res.ok) {
console.error(await res.text());
return Err(error);
}
const json = (await res.json()) as unknown;
const parsed = schema.safeParse(json);
if (!parsed.success) {
console.error(
parsed.error.issues.map((i) => `${i.code} | ${i.message}`).join("\n"),
);
return Err(error);
}
return Ok(json as z.infer<TSchema>);
};

53
src/utils/types.ts Normal file
View File

@@ -0,0 +1,53 @@
import { z } from "astro/zod";
export type Result<Ok, Err> =
| { ok: true; value: Ok }
| { ok: false; error: Err };
export const Ok = <Ok>(value: Ok) => ({ ok: true, value } as const);
export const Err = <Err>(error: Err) => ({ ok: false, error } as const);
export type Contributor = z.infer<typeof ContributorSchema>;
export const ContributorSchema = z.object({
id: z.string(),
name: z.string(),
roles: z.array(z.string()),
isAdmin: z.boolean(),
isCore: z.boolean(),
isBacker: z.boolean(),
since: z.string(),
image: z.string(),
description: z.string().nullable(),
collectiveSlug: z.string(),
totalAmountDonated: z.number(),
type: z.string(),
publicMessage: z.string().nullable(),
isIncognito: z.boolean(),
__typename: z.string(),
});
export type Contributors = z.infer<typeof ContributorsSchema>;
export const ContributorsSchema = z.object({
totalCount: z.number(),
nodes: z.array(ContributorSchema),
__typename: z.string(),
});
export type OpenCollectiveAccount = z.infer<typeof OpenCollectiveAccountSchema>;
export const OpenCollectiveAccountSchema = z.object({
id: z.string(),
currency: z.string(),
slug: z.string(),
contributors: ContributorsSchema,
__typename: z.string(),
});
export type Plugin = z.infer<typeof PluginSchema>;
export const PluginSchema = z.object({
description: z.string(),
hash: z.string(),
name: z.string(),
author: z.array(z.string()),
link: z.string().url(),
example: z.string(),
version: z.string(),
});