VIbe-code some optimizations

This commit is contained in:
Thomas Camlong
2025-11-17 13:36:00 +01:00
parent 4b0a9313a5
commit 2e20511872

View File

@@ -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<IconFile> {
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<IconFile> {
* 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<IconFile> => {
return getAllIconsCached()
})
return getAllIconsCached();
});
/**
* Gets a list of all icon names.
*/
export const getIconNames = async (): Promise<string[]> => {
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<IconWithName[]> {
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<IconWithName | null> {
export async function getIconData(
iconName: string,
): Promise<IconWithName | null> {
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<number, AuthorData> = {}
const authorDataCache: Record<number, AuthorData> = {};
/**
* Cached version of fetchAuthorData
@@ -155,12 +169,12 @@ const authorDataCache: Record<number, AuthorData> = {}
*/
export async function getAuthorData(authorId: number): Promise<AuthorData> {
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<AuthorData> {
*/
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<IconWithName[]> {
export async function getRecentlyAddedIcons(
limit = 8,
): Promise<IconWithName[]> {
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;
}
}