diff --git a/web/biome.jsonc b/web/biome.jsonc index 498148cf..aa97117f 100644 --- a/web/biome.jsonc +++ b/web/biome.jsonc @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.5/schema.json", "vcs": { "enabled": true, "clientKind": "git", diff --git a/web/src/app/community/[icon]/opengraph-image.tsx b/web/src/app/community/[icon]/opengraph-image.tsx index 61da7c4c..94f17d72 100644 --- a/web/src/app/community/[icon]/opengraph-image.tsx +++ b/web/src/app/community/[icon]/opengraph-image.tsx @@ -2,26 +2,16 @@ import { permanentRedirect, redirect } from "next/navigation" import { ImageResponse } from "next/og" import { getCommunityGalleryRecord, getCommunitySubmissionByName, getCommunitySubmissions } from "@/lib/community" -export const revalidate = 21600 // 6 hours - -export async function generateStaticParams() { - const icons = await getCommunitySubmissions() - const validIcons = icons.filter((icon) => icon.name) - if (process.env.CI_MODE === "false") { - return validIcons.slice(0, 5).map((icon) => ({ - icon: icon.name, - })) - } - return validIcons.map((icon) => ({ - icon: icon.name, - })) -} +export const dynamic = "force-dynamic"; export const size = { width: 1200, height: 630, } +export const alt = "Community Icon Open Graph Image"; +export const contentType = "image/png"; + export default async function Image({ params }: { params: Promise<{ icon: string }> }) { const { icon } = await params @@ -137,7 +127,7 @@ export default async function Image({ params }: { params: Promise<{ icon: string const iconUrl = iconDataBuffer ? `data:image/png;base64,${iconDataBuffer.toString("base64")}` - : `https://placehold.co/600x400?text=${formattedIconName}` + : `https://placehold.co/600x400?text=${formattedIconName}`; return new ImageResponse(
({ - icon, - })) - } - return Object.keys(iconsData).map((icon) => ({ - icon, - })) -} +export const dynamic = "force-dynamic"; export const size = { width: 1200, height: 630, -} -export default async function Image({ params }: { params: Promise<{ icon: string }> }) { - const { icon } = await params +}; + +export const alt = "Icon Open Graph Image"; +export const contentType = "image/png"; +export default async function Image({ + params, +}: { + params: Promise<{ icon: string }>; +}) { + const { icon } = await params; if (!icon) { - console.error(`[Opengraph Image] Icon not found for ${icon}`) + console.error(`[Opengraph Image] Icon not found for ${icon}`); return new ImageResponse(
, { ...size }, - ) + ); } - const iconsData = await getAllIcons() - const totalIcons = Object.keys(iconsData).length - const index = Object.keys(iconsData).indexOf(icon) + const iconsData = await getAllIcons(); + const totalIcons = Object.keys(iconsData).length; + const index = Object.keys(iconsData).indexOf(icon); // Format the icon name for display const formattedIconName = icon .split("-") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" ") + .join(" "); // Read the icon file from local filesystem - let iconData: Buffer | null = null + let iconData: Buffer | null = null; try { - const iconPath = join(process.cwd(), `../png/${icon}.png`) - console.log(`Generating opengraph image for ${icon} (${index + 1} / ${totalIcons}) from path ${iconPath}`) - iconData = await readFile(iconPath) + const iconPath = join(process.cwd(), `../png/${icon}.png`); + console.log( + `Generating opengraph image for ${icon} (${index + 1} / ${totalIcons}) from path ${iconPath}`, + ); + iconData = await readFile(iconPath); } catch (_error) { - console.error(`Icon ${icon} was not found locally`) + console.error(`Icon ${icon} was not found locally`); } // Convert the image data to a data URL or use placeholder - const iconUrl = iconData ? `data:image/png;base64,${iconData.toString("base64")}` : null + const iconUrl = iconData + ? `data:image/png;base64,${iconData.toString("base64")}` + : null; return new ImageResponse(
{formattedIconName} { try { - const response = await fetch(METADATA_URL) + const response = await fetch(METADATA_URL); 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."); } } @@ -30,55 +33,57 @@ export async function getAllIcons(): Promise { */ 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; } } @@ -91,26 +96,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", @@ -118,7 +128,7 @@ async function fetchAuthorData(authorId: number) { html_url: "https://github.com", name: "Unknown User", bio: null, - } + }; } } @@ -148,32 +158,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; } } diff --git a/web/src/lib/errors.ts b/web/src/lib/errors.ts index 7c7b203a..e1f6eceb 100644 --- a/web/src/lib/errors.ts +++ b/web/src/lib/errors.ts @@ -10,6 +10,3 @@ export class ApiError extends Error { this.status = status } } - - -