94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
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<ProjectHealthSnapshot | null> {
|
|
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<PortfolioProjectView[]> {
|
|
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);
|
|
}
|
|
}
|