From 2e20511872c462db00ae1179a5a13252f65727e0 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Mon, 17 Nov 2025 13:36:00 +0100 Subject: [PATCH] VIbe-code some optimizations --- web/src/lib/api.ts | 131 ++++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index c9fa0b5b..74016469 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -1,8 +1,8 @@ -import { unstable_cache } from "next/cache" -import { cache } from "react" -import { METADATA_URL } from "@/constants" -import { ApiError } from "@/lib/errors" -import type { AuthorData, IconFile, IconWithName } from "@/types/icons" +import { unstable_cache } from "next/cache"; +import { cache } from "react"; +import { METADATA_URL } from "@/constants"; +import { ApiError } from "@/lib/errors"; +import type { AuthorData, IconFile, IconWithName } from "@/types/icons"; /** * Raw fetch function for icon data (without caching) @@ -11,19 +11,22 @@ async function fetchAllIconsRaw(): Promise { try { const response = await fetch(METADATA_URL, { next: { revalidate: 3600 }, - }) + }); if (!response.ok) { - throw new ApiError(`Failed to fetch icons: ${response.statusText}`, response.status) + throw new ApiError( + `Failed to fetch icons: ${response.statusText}`, + response.status, + ); } - return (await response.json()) as IconFile + return (await response.json()) as IconFile; } catch (error) { if (error instanceof ApiError) { - throw error + throw error; } - console.error("Error fetching icons:", error) - throw new ApiError("Failed to fetch icons data. Please try again later.") + console.error("Error fetching icons:", error); + throw new ApiError("Failed to fetch icons data. Please try again later."); } } @@ -31,10 +34,14 @@ async function fetchAllIconsRaw(): Promise { * Cached version using unstable_cache for build-time caching * Revalidates every hour (3600 seconds) */ -const getAllIconsCached = unstable_cache(async () => fetchAllIconsRaw(), ["all-icons"], { - revalidate: 3600, - tags: ["icons"], -}) +const getAllIconsCached = unstable_cache( + async () => fetchAllIconsRaw(), + ["all-icons"], + { + revalidate: 3600, + tags: ["icons"], + }, +); /** * Fetches all icon data from the metadata.json file @@ -42,63 +49,65 @@ const getAllIconsCached = unstable_cache(async () => fetchAllIconsRaw(), ["all-i * This prevents duplicate fetches within the same request and across builds */ export const getAllIcons = cache(async (): Promise => { - return getAllIconsCached() -}) + return getAllIconsCached(); +}); /** * Gets a list of all icon names. */ export const getIconNames = async (): Promise => { try { - const iconsData = await getAllIcons() - return Object.keys(iconsData) + const iconsData = await getAllIcons(); + return Object.keys(iconsData); } catch (error) { - console.error("Error getting icon names:", error) - throw error + console.error("Error getting icon names:", error); + throw error; } -} +}; /** * Converts icon data to an array format for easier rendering */ export async function getIconsArray(): Promise { try { - const iconsData = await getAllIcons() + const iconsData = await getAllIcons(); return Object.entries(iconsData) .map(([name, data]) => ({ name, data, })) - .sort((a, b) => a.name.localeCompare(b.name)) + .sort((a, b) => a.name.localeCompare(b.name)); } catch (error) { - console.error("Error getting icons array:", error) - throw error + console.error("Error getting icons array:", error); + throw error; } } /** * Fetches data for a specific icon */ -export async function getIconData(iconName: string): Promise { +export async function getIconData( + iconName: string, +): Promise { try { - const iconsData = await getAllIcons() - const iconData = iconsData[iconName] + const iconsData = await getAllIcons(); + const iconData = iconsData[iconName]; if (!iconData) { - throw new ApiError(`Icon '${iconName}' not found`, 404) + throw new ApiError(`Icon '${iconName}' not found`, 404); } return { name: iconName, data: iconData, - } + }; } catch (error) { if (error instanceof ApiError && error.status === 404) { - return null + return null; } - console.error("Error getting icon data:", error) - throw error + console.error("Error getting icon data:", error); + throw error; } } @@ -111,26 +120,31 @@ async function fetchAuthorData(authorId: number) { headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, }, - }) + }); if (!response.ok) { // If unauthorized or other error, return a default user object if (response.status === 401 || response.status === 403) { - console.warn(`GitHub API rate limit or authorization issue: ${response.statusText}`) + console.warn( + `GitHub API rate limit or authorization issue: ${response.statusText}`, + ); return { login: "unknown", avatar_url: "https://avatars.githubusercontent.com/u/0", html_url: "https://github.com", name: "Unknown User", bio: null, - } + }; } - throw new ApiError(`Failed to fetch author data: ${response.statusText}`, response.status) + throw new ApiError( + `Failed to fetch author data: ${response.statusText}`, + response.status, + ); } - return response.json() + return response.json(); } catch (error) { - console.error("Error fetching author data:", error) + console.error("Error fetching author data:", error); // Even for unexpected errors, return a default user to prevent page failures return { login: "unknown", @@ -138,11 +152,11 @@ async function fetchAuthorData(authorId: number) { html_url: "https://github.com", name: "Unknown User", bio: null, - } + }; } } -const authorDataCache: Record = {} +const authorDataCache: Record = {}; /** * Cached version of fetchAuthorData @@ -155,12 +169,12 @@ const authorDataCache: Record = {} */ export async function getAuthorData(authorId: number): Promise { if (authorDataCache[authorId]) { - return authorDataCache[authorId] + return authorDataCache[authorId]; } - const data = await fetchAuthorData(authorId) - authorDataCache[authorId] = data - return data + const data = await fetchAuthorData(authorId); + authorDataCache[authorId] = data; + return data; } /** @@ -168,32 +182,37 @@ export async function getAuthorData(authorId: number): Promise { */ export async function getTotalIcons() { try { - const iconsData = await getAllIcons() + const iconsData = await getAllIcons(); return { totalIcons: Object.keys(iconsData).length, - } + }; } catch (error) { - console.error("Error getting total icons:", error) - throw error + console.error("Error getting total icons:", error); + throw error; } } /** * Fetches recently added icons sorted by timestamp */ -export async function getRecentlyAddedIcons(limit = 8): Promise { +export async function getRecentlyAddedIcons( + limit = 8, +): Promise { try { - const icons = await getIconsArray() + const icons = await getIconsArray(); return icons .sort((a, b) => { // Sort by timestamp in descending order (newest first) - return new Date(b.data.update.timestamp).getTime() - new Date(a.data.update.timestamp).getTime() + return ( + new Date(b.data.update.timestamp).getTime() - + new Date(a.data.update.timestamp).getTime() + ); }) - .slice(0, limit) + .slice(0, limit); } catch (error) { - console.error("Error getting recently added icons:", error) - throw error + console.error("Error getting recently added icons:", error); + throw error; } }