From dd400cbef59bf9c8ca372b2ddef0b162f5b70005 Mon Sep 17 00:00:00 2001 From: svemagie <869694+svemagie@users.noreply.github.com> Date: Sun, 8 Mar 2026 06:36:20 +0100 Subject: [PATCH] Fetch CV data via API with file fallback --- _data/cv.js | 78 ++++++++++++++++++++++++++++--------- _data/cvPageConfig.js | 66 ++++++++++++++++++++++++++----- theme/_data/cv.js | 78 ++++++++++++++++++++++++++++--------- theme/_data/cvPageConfig.js | 66 ++++++++++++++++++++++++++----- 4 files changed, 232 insertions(+), 56 deletions(-) diff --git a/_data/cv.js b/_data/cv.js index a64d08f..ec78d39 100644 --- a/_data/cv.js +++ b/_data/cv.js @@ -1,37 +1,79 @@ /** - * CV Data — reads from indiekit-endpoint-cv plugin data file. + * CV Data * - * The CV plugin writes content/.indiekit/cv.json on every save - * and on startup. Eleventy reads that file here. + * API-first for split backend/frontend deployments: + * - Try Indiekit public API (`/cvapi/data.json`, fallback `/cv/data.json`) + * - Fallback to local plugin file (`content/.indiekit/cv.json`) * - * Falls back to empty defaults if no plugin is installed. + * Returns empty defaults if neither source is available. */ +import EleventyFetch from "@11ty/eleventy-fetch"; import { readFileSync } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); +const INDIEKIT_URL = + process.env.INDIEKIT_URL || process.env.SITE_URL || "https://example.com"; -export default function () { +const EMPTY_CV = { + lastUpdated: null, + experience: [], + projects: [], + skills: {}, + skillTypes: {}, + languages: [], + education: [], + interests: {}, + interestTypes: {}, +}; + +async function fetchFromIndiekit(path) { + const urls = [ + `${INDIEKIT_URL}/cvapi/${path}`, + `${INDIEKIT_URL}/cv/${path}`, + ]; + + for (const url of urls) { + try { + console.log(`[cv] Fetching from Indiekit: ${url}`); + const data = await EleventyFetch(url, { + duration: "15m", + type: "json", + }); + console.log(`[cv] Indiekit ${path} success via ${url}`); + return data; + } catch (error) { + console.log(`[cv] Indiekit API unavailable at ${url}: ${error.message}`); + } + } + + return null; +} + +function readLocalCvFile() { try { const cvPath = resolve(__dirname, "..", "content", ".indiekit", "cv.json"); const raw = readFileSync(cvPath, "utf8"); const data = JSON.parse(raw); - console.log("[cv] Loaded CV data from plugin"); + console.log("[cv] Loaded CV data from local plugin file"); return data; } catch { - // No CV plugin data file — return empty defaults - return { - lastUpdated: null, - experience: [], - projects: [], - skills: {}, - skillTypes: {}, - languages: [], - education: [], - interests: {}, - interestTypes: {}, - }; + return null; } } + +export default async function () { + const apiData = await fetchFromIndiekit("data.json"); + if (apiData && typeof apiData === "object") { + return { ...EMPTY_CV, ...apiData }; + } + + const localData = readLocalCvFile(); + if (localData && typeof localData === "object") { + return { ...EMPTY_CV, ...localData }; + } + + return EMPTY_CV; +} diff --git a/_data/cvPageConfig.js b/_data/cvPageConfig.js index 456b388..b582fd9 100644 --- a/_data/cvPageConfig.js +++ b/_data/cvPageConfig.js @@ -1,29 +1,75 @@ /** * CV Page Configuration Data - * Reads config from indiekit-endpoint-cv plugin CV page builder. - * Falls back to null — cv.njk then uses the default hardcoded layout. * - * The CV plugin writes a .indiekit/cv-page.json file that Eleventy watches. - * On change, a rebuild picks up the new config, allowing layout changes - * without a Docker rebuild. + * API-first for split backend/frontend deployments: + * - Try Indiekit public API (`/cvapi/page.json`, fallback `/cv/page.json`) + * - Fallback to local plugin file (`content/.indiekit/cv-page.json`) + * + * Falls back to null so cv.njk can use the hardcoded default layout. */ +import EleventyFetch from "@11ty/eleventy-fetch"; import { readFileSync } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); +const INDIEKIT_URL = + process.env.INDIEKIT_URL || process.env.SITE_URL || "https://example.com"; -export default function () { +async function fetchFromIndiekit(path) { + const urls = [ + `${INDIEKIT_URL}/cvapi/${path}`, + `${INDIEKIT_URL}/cv/${path}`, + ]; + + for (const url of urls) { + try { + console.log(`[cvPageConfig] Fetching from Indiekit: ${url}`); + const data = await EleventyFetch(url, { + duration: "15m", + type: "json", + }); + console.log(`[cvPageConfig] Indiekit ${path} success via ${url}`); + return data; + } catch (error) { + console.log( + `[cvPageConfig] Indiekit API unavailable at ${url}: ${error.message}` + ); + } + } + + return null; +} + +function readLocalConfigFile() { try { - // Resolve via the content/ symlink relative to the Eleventy project - const configPath = resolve(__dirname, "..", "content", ".indiekit", "cv-page.json"); + const configPath = resolve( + __dirname, + "..", + "content", + ".indiekit", + "cv-page.json" + ); const raw = readFileSync(configPath, "utf8"); const config = JSON.parse(raw); - console.log("[cvPageConfig] Loaded CV page builder config"); + console.log("[cvPageConfig] Loaded local CV page builder config"); return config; } catch { - // No CV page builder config — fall back to hardcoded layout in cv.njk return null; } } + +export default async function () { + const apiConfig = await fetchFromIndiekit("page.json"); + if (apiConfig && typeof apiConfig === "object") { + return apiConfig; + } + + const localConfig = readLocalConfigFile(); + if (localConfig && typeof localConfig === "object") { + return localConfig; + } + + return null; +} diff --git a/theme/_data/cv.js b/theme/_data/cv.js index a64d08f..ec78d39 100644 --- a/theme/_data/cv.js +++ b/theme/_data/cv.js @@ -1,37 +1,79 @@ /** - * CV Data — reads from indiekit-endpoint-cv plugin data file. + * CV Data * - * The CV plugin writes content/.indiekit/cv.json on every save - * and on startup. Eleventy reads that file here. + * API-first for split backend/frontend deployments: + * - Try Indiekit public API (`/cvapi/data.json`, fallback `/cv/data.json`) + * - Fallback to local plugin file (`content/.indiekit/cv.json`) * - * Falls back to empty defaults if no plugin is installed. + * Returns empty defaults if neither source is available. */ +import EleventyFetch from "@11ty/eleventy-fetch"; import { readFileSync } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); +const INDIEKIT_URL = + process.env.INDIEKIT_URL || process.env.SITE_URL || "https://example.com"; -export default function () { +const EMPTY_CV = { + lastUpdated: null, + experience: [], + projects: [], + skills: {}, + skillTypes: {}, + languages: [], + education: [], + interests: {}, + interestTypes: {}, +}; + +async function fetchFromIndiekit(path) { + const urls = [ + `${INDIEKIT_URL}/cvapi/${path}`, + `${INDIEKIT_URL}/cv/${path}`, + ]; + + for (const url of urls) { + try { + console.log(`[cv] Fetching from Indiekit: ${url}`); + const data = await EleventyFetch(url, { + duration: "15m", + type: "json", + }); + console.log(`[cv] Indiekit ${path} success via ${url}`); + return data; + } catch (error) { + console.log(`[cv] Indiekit API unavailable at ${url}: ${error.message}`); + } + } + + return null; +} + +function readLocalCvFile() { try { const cvPath = resolve(__dirname, "..", "content", ".indiekit", "cv.json"); const raw = readFileSync(cvPath, "utf8"); const data = JSON.parse(raw); - console.log("[cv] Loaded CV data from plugin"); + console.log("[cv] Loaded CV data from local plugin file"); return data; } catch { - // No CV plugin data file — return empty defaults - return { - lastUpdated: null, - experience: [], - projects: [], - skills: {}, - skillTypes: {}, - languages: [], - education: [], - interests: {}, - interestTypes: {}, - }; + return null; } } + +export default async function () { + const apiData = await fetchFromIndiekit("data.json"); + if (apiData && typeof apiData === "object") { + return { ...EMPTY_CV, ...apiData }; + } + + const localData = readLocalCvFile(); + if (localData && typeof localData === "object") { + return { ...EMPTY_CV, ...localData }; + } + + return EMPTY_CV; +} diff --git a/theme/_data/cvPageConfig.js b/theme/_data/cvPageConfig.js index 456b388..b582fd9 100644 --- a/theme/_data/cvPageConfig.js +++ b/theme/_data/cvPageConfig.js @@ -1,29 +1,75 @@ /** * CV Page Configuration Data - * Reads config from indiekit-endpoint-cv plugin CV page builder. - * Falls back to null — cv.njk then uses the default hardcoded layout. * - * The CV plugin writes a .indiekit/cv-page.json file that Eleventy watches. - * On change, a rebuild picks up the new config, allowing layout changes - * without a Docker rebuild. + * API-first for split backend/frontend deployments: + * - Try Indiekit public API (`/cvapi/page.json`, fallback `/cv/page.json`) + * - Fallback to local plugin file (`content/.indiekit/cv-page.json`) + * + * Falls back to null so cv.njk can use the hardcoded default layout. */ +import EleventyFetch from "@11ty/eleventy-fetch"; import { readFileSync } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); +const INDIEKIT_URL = + process.env.INDIEKIT_URL || process.env.SITE_URL || "https://example.com"; -export default function () { +async function fetchFromIndiekit(path) { + const urls = [ + `${INDIEKIT_URL}/cvapi/${path}`, + `${INDIEKIT_URL}/cv/${path}`, + ]; + + for (const url of urls) { + try { + console.log(`[cvPageConfig] Fetching from Indiekit: ${url}`); + const data = await EleventyFetch(url, { + duration: "15m", + type: "json", + }); + console.log(`[cvPageConfig] Indiekit ${path} success via ${url}`); + return data; + } catch (error) { + console.log( + `[cvPageConfig] Indiekit API unavailable at ${url}: ${error.message}` + ); + } + } + + return null; +} + +function readLocalConfigFile() { try { - // Resolve via the content/ symlink relative to the Eleventy project - const configPath = resolve(__dirname, "..", "content", ".indiekit", "cv-page.json"); + const configPath = resolve( + __dirname, + "..", + "content", + ".indiekit", + "cv-page.json" + ); const raw = readFileSync(configPath, "utf8"); const config = JSON.parse(raw); - console.log("[cvPageConfig] Loaded CV page builder config"); + console.log("[cvPageConfig] Loaded local CV page builder config"); return config; } catch { - // No CV page builder config — fall back to hardcoded layout in cv.njk return null; } } + +export default async function () { + const apiConfig = await fetchFromIndiekit("page.json"); + if (apiConfig && typeof apiConfig === "object") { + return apiConfig; + } + + const localConfig = readLocalConfigFile(); + if (localConfig && typeof localConfig === "object") { + return localConfig; + } + + return null; +}