import { readFile } from "node:fs/promises"; import { resolve } from "node:path"; import portfolioData from "@/public/portfolio-data.json"; import { createProjectHealthSnapshot, type DemoAvailability, getDemoAvailability, PROJECT_HEALTH_MAX_AGE_MS, type ProjectHealthSnapshot, } from "./project-health"; export type PortfolioData = typeof portfolioData; export type PortfolioProject = PortfolioData["projects"][number]; export type PortfolioExperience = PortfolioData["experiences"][number]; export type PortfolioSkillCategory = PortfolioData["skills"]["technical"][number]; export type PortfolioProjectView = PortfolioProject & { demoAvailability: DemoAvailability; demoCheckedAt: string | null; }; export const portfolio = portfolioData; export function getFeaturedProjects( snapshot: ProjectHealthSnapshot | null, ): PortfolioProjectView[] { return portfolio.projects .filter(({ featured }) => featured !== false) .map((project) => { const check = snapshot?.projects[project.id]; return { ...project, demoAvailability: getDemoAvailability(project.demoUrl, check), demoCheckedAt: check?.checkedAt ?? null, }; }); } function isSnapshotFresh( snapshot: ProjectHealthSnapshot | null, now = new Date(), ) { if (!snapshot) return false; const checkedAt = Date.parse(snapshot.checkedAt); return ( Number.isFinite(checkedAt) && now.getTime() - checkedAt <= PROJECT_HEALTH_MAX_AGE_MS ); } export async function loadProjectHealthSnapshot(): Promise { try { const path = resolve(process.cwd(), "data/project-health.json"); return JSON.parse(await readFile(path, "utf8")) as ProjectHealthSnapshot; } catch { return null; } } export async function loadFeaturedProjects(): Promise { const snapshot = await loadProjectHealthSnapshot(); if (process.env.NODE_ENV !== "development" || isSnapshotFresh(snapshot)) { return getFeaturedProjects(snapshot); } const projectDemos = portfolio.projects.reduce< Array<{ id: string; demoUrl: string }> >((demos, project) => { if (project.demoUrl) demos.push({ id: project.id, demoUrl: project.demoUrl }); return demos; }, []); try { const refreshed = await createProjectHealthSnapshot(projectDemos); const projects = { ...refreshed.projects }; for (const [id, check] of Object.entries(projects)) { if (!check.error && check.status !== null) continue; const previous = snapshot?.projects[id]; if (previous && !previous.error && previous.status !== null) { projects[id] = previous; } else { delete projects[id]; } } return getFeaturedProjects({ ...refreshed, projects }); } catch { return getFeaturedProjects(snapshot); } }