diff --git a/_data/githubActivity.js b/_data/githubActivity.js index 1857904..303a197 100644 --- a/_data/githubActivity.js +++ b/_data/githubActivity.js @@ -1,298 +1,91 @@ /** - * GitHub Activity Data - * Fetches from Indiekit's endpoint-github public API - * Falls back to direct GitHub API if Indiekit is unavailable + * Gitea Activity Data + * Fetches commits and repos from self-hosted Gitea instance */ import EleventyFetch from "@11ty/eleventy-fetch"; -const GITHUB_USERNAME = process.env.GITHUB_USERNAME || ""; -const INDIEKIT_URL = process.env.SITE_URL || "https://example.com"; +const GITEA_URL = process.env.GITEA_URL || "https://gitea.giersig.eu"; +const GITEA_ORG = process.env.GITEA_ORG || "giersig.eu"; +const GITEA_REPOS = (process.env.GITEA_REPOS || "indiekit-blog,indiekit-server").split(",").filter(Boolean); -// Fallback featured repos if Indiekit API unavailable (from env: comma-separated) -const FALLBACK_FEATURED_REPOS = process.env.GITHUB_FEATURED_REPOS?.split(",").filter(Boolean) || []; - -/** - * Fetch from Indiekit's public GitHub API endpoint - */ -async function fetchFromIndiekit(endpoint) { - const urls = [ - `${INDIEKIT_URL}/github/api/${endpoint}`, - `${INDIEKIT_URL}/githubapi/api/${endpoint}`, - ]; - - for (const url of urls) { - try { - console.log(`[githubActivity] Fetching from Indiekit: ${url}`); - const data = await EleventyFetch(url, { - duration: "15m", - type: "json", - }); - console.log(`[githubActivity] Indiekit ${endpoint} success via ${url}`); - return data; - } catch (error) { - console.log( - `[githubActivity] Indiekit API unavailable for ${endpoint} at ${url}: ${error.message}` - ); - } - } - - return null; -} - -/** - * Fetch from GitHub API directly - */ -async function fetchFromGitHub(endpoint) { - const url = `https://api.github.com${endpoint}`; - const headers = { - Accept: "application/vnd.github.v3+json", - "User-Agent": "Eleventy-Site", - }; - - if (process.env.GITHUB_TOKEN) { - headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`; - } - - return await EleventyFetch(url, { - duration: "15m", - type: "json", - fetchOptions: { headers }, - }); -} - -/** - * Truncate text with ellipsis - */ function truncate(text, maxLength = 80) { if (!text || text.length <= maxLength) return text || ""; return text.slice(0, maxLength - 1) + "..."; } -/** - * Extract commits from push events - */ -function extractCommits(events) { - if (!Array.isArray(events)) return []; +async function fetchGiteaCommits() { + const allCommits = []; - // Style commit keywords - const styleKeywords = [ - "style", "css", "tailwind", "design", "layout", "responsive", "dark mode", "typography", "color", "palette", "theme" - ]; - return events - .filter((event) => event.type === "PushEvent") - .flatMap((event) => - (event.payload?.commits || []).map((commit) => { - const msg = commit.message.split("\n")[0]; - const lowerMsg = msg.toLowerCase(); - const isStyle = styleKeywords.some((kw) => lowerMsg.includes(kw)); - return { - sha: commit.sha.slice(0, 7), - message: truncate(msg), - url: `https://github.com/${event.repo.name}/commit/${commit.sha}`, - repo: event.repo.name, - repoUrl: `https://github.com/${event.repo.name}`, - date: event.created_at, - category: isStyle ? "style" : undefined, - }; - }) - ) - .slice(0, 10); -} - -/** - * Extract PRs/Issues from events - */ -function extractContributions(events) { - if (!Array.isArray(events)) return []; - - return events - .filter( - (event) => - (event.type === "PullRequestEvent" || event.type === "IssuesEvent") && - event.payload?.action === "opened" - ) - .map((event) => { - const item = event.payload.pull_request || event.payload.issue; - return { - type: event.type === "PullRequestEvent" ? "pr" : "issue", - title: truncate(item?.title), - url: item?.html_url, - repo: event.repo.name, - repoUrl: `https://github.com/${event.repo.name}`, - number: item?.number, - date: event.created_at, - }; - }) - .slice(0, 10); -} - -/** - * Format starred repos - */ -function formatStarred(repos) { - if (!Array.isArray(repos)) return []; - - return repos.map((repo) => ({ - name: repo.full_name, - description: truncate(repo.description, 120), - url: repo.html_url, - stars: repo.stargazers_count, - language: repo.language, - topics: repo.topics?.slice(0, 5) || [], - })); -} - -/** - * Fetch featured repos directly from GitHub (fallback) - */ -async function fetchFeaturedFromGitHub(repoList) { - const featured = []; - - for (const repoFullName of repoList) { + for (const repo of GITEA_REPOS) { try { - const repo = await fetchFromGitHub(`/repos/${repoFullName}`); - let commits = []; - try { - const commitsData = await fetchFromGitHub( - `/repos/${repoFullName}/commits?per_page=5` - ); - commits = commitsData.map((c) => ({ - sha: c.sha.slice(0, 7), - message: truncate(c.commit.message.split("\n")[0]), - url: c.html_url, - date: c.commit.author.date, - })); - } catch (e) { - console.log(`[githubActivity] Could not fetch commits for ${repoFullName}`); - } + const url = `${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${repo}/commits?limit=15`; + console.log(`[giteaActivity] Fetching commits: ${url}`); + const commits = await EleventyFetch(url, { duration: "15m", type: "json" }); - featured.push({ - fullName: repo.full_name, - name: repo.name, - description: repo.description, - url: repo.html_url, - stars: repo.stargazers_count, - forks: repo.forks_count, - language: repo.language, - isPrivate: repo.private, - commits, - }); - } catch (error) { - console.log(`[githubActivity] Could not fetch ${repoFullName}: ${error.message}`); + for (const c of Array.isArray(commits) ? commits : []) { + const msg = (c.commit?.message || "").split("\n")[0]; + allCommits.push({ + sha: c.sha.slice(0, 7), + message: truncate(msg), + url: c.html_url, + repo: `${GITEA_ORG}/${repo}`, + repoUrl: `${GITEA_URL}/${GITEA_ORG}/${repo}`, + date: c.created || c.commit?.author?.date, + }); + } + } catch (e) { + console.log(`[giteaActivity] Commits fetch failed for ${repo}: ${e.message}`); } } - return featured; + return allCommits.sort((a, b) => new Date(b.date) - new Date(a.date)).slice(0, 10); } -/** - * Fetch commits directly from user's recently pushed repos - * Fallback when events API doesn't include commit details - */ -async function fetchCommitsFromRepos(username, limit = 10) { +async function fetchGiteaFeatured() { try { - const repos = await fetchFromGitHub( - `/users/${username}/repos?sort=pushed&per_page=5` - ); + const url = `${GITEA_URL}/api/v1/orgs/${GITEA_ORG}/repos?limit=10&sort=newest`; + const repos = await EleventyFetch(url, { duration: "15m", type: "json" }); - if (!Array.isArray(repos) || repos.length === 0) { - return []; - } - - const allCommits = []; - for (const repo of repos.slice(0, 5)) { - try { - const repoCommits = await fetchFromGitHub( - `/repos/${repo.full_name}/commits?per_page=5` - ); - for (const c of repoCommits) { - allCommits.push({ - sha: c.sha.slice(0, 7), - message: truncate(c.commit?.message?.split("\n")[0]), - url: c.html_url, - repo: repo.full_name, - repoUrl: repo.html_url, - date: c.commit?.author?.date, - }); - } - } catch { - // Skip repos we can't access - } - } - - // Sort by date and limit - return allCommits - .sort((a, b) => new Date(b.date) - new Date(a.date)) - .slice(0, limit); - } catch (error) { - console.log(`[githubActivity] Could not fetch commits from repos: ${error.message}`); + return (Array.isArray(repos) ? repos : []) + .filter((r) => !r.fork && !r.private) + .slice(0, 5) + .map((r) => ({ + fullName: r.full_name, + name: r.name, + description: r.description, + url: r.html_url, + stars: r.stars_count || r.stargazers_count || 0, + forks: r.forks_count || 0, + language: r.language, + })); + } catch (e) { + console.log(`[giteaActivity] Featured fetch failed: ${e.message}`); return []; } } export default async function () { try { - console.log("[githubActivity] Fetching GitHub data..."); + console.log("[giteaActivity] Fetching Gitea data..."); - // Try Indiekit public API first - const [indiekitStars, indiekitCommits, indiekitContributions, indiekitActivity, indiekitFeatured] = - await Promise.all([ - fetchFromIndiekit("stars"), - fetchFromIndiekit("commits"), - fetchFromIndiekit("contributions"), - fetchFromIndiekit("activity"), - fetchFromIndiekit("featured"), - ]); - - // Check if Indiekit API is available - const hasIndiekitData = - indiekitStars?.stars || - indiekitCommits?.commits || - indiekitFeatured?.featured; - - if (hasIndiekitData) { - console.log("[githubActivity] Using Indiekit API data"); - return { - stars: indiekitStars?.stars || [], - commits: indiekitCommits?.commits || [], - contributions: indiekitContributions?.contributions || [], - activity: indiekitActivity?.activity || [], - featured: indiekitFeatured?.featured || [], - source: "indiekit", - }; - } - - // Fallback to direct GitHub API - console.log("[githubActivity] Falling back to GitHub API"); - - const [events, starred, featured] = await Promise.all([ - fetchFromGitHub(`/users/${GITHUB_USERNAME}/events/public?per_page=50`), - fetchFromGitHub(`/users/${GITHUB_USERNAME}/starred?per_page=20&sort=created`), - fetchFeaturedFromGitHub(FALLBACK_FEATURED_REPOS), + const [commits, featured] = await Promise.all([ + fetchGiteaCommits(), + fetchGiteaFeatured(), ]); - // Try to extract commits from events first - let commits = extractCommits(events || []); - - // If events API didn't have commits, fetch directly from repos - if (commits.length === 0 && GITHUB_USERNAME) { - console.log("[githubActivity] Events API returned no commits, fetching from repos"); - commits = await fetchCommitsFromRepos(GITHUB_USERNAME, 10); - } - return { - stars: formatStarred(starred || []), commits, - contributions: extractContributions(events || []), + stars: [], + contributions: [], featured, - source: "github", + source: "gitea", }; } catch (error) { - console.error("[githubActivity] Error:", error.message); + console.error("[giteaActivity] Error:", error.message); return { - stars: [], commits: [], + stars: [], contributions: [], featured: [], source: "error", diff --git a/_data/githubRepos.js b/_data/githubRepos.js index 9b1bc34..3a77298 100644 --- a/_data/githubRepos.js +++ b/_data/githubRepos.js @@ -1,48 +1,41 @@ /** - * GitHub Repos Data - * Fetches public repositories from GitHub API + * Gitea Repos Data + * Fetches public repositories from Gitea org API */ import { cachedFetch } from "../lib/data-fetch.js"; -export default async function () { - const username = process.env.GITHUB_USERNAME || ""; +const GITEA_URL = process.env.GITEA_URL || "https://gitea.giersig.eu"; +const GITEA_ORG = process.env.GITEA_ORG || "giersig.eu"; +export default async function () { try { - // Fetch public repos, sorted by updated date - const url = `https://api.github.com/users/${username}/repos?sort=updated&per_page=10&type=owner`; + const url = `${GITEA_URL}/api/v1/orgs/${GITEA_ORG}/repos?limit=10&sort=newest`; const repos = await cachedFetch(url, { - duration: "1h", // Cache for 1 hour + duration: "1h", type: "json", - fetchOptions: { - headers: { - Accept: "application/vnd.github.v3+json", - "User-Agent": "Eleventy-Site", - }, - }, }); - // Filter and transform repos return repos - .filter((repo) => !repo.fork && !repo.private) // Exclude forks and private repos + .filter((repo) => !repo.fork && !repo.private) .map((repo) => ({ name: repo.name, full_name: repo.full_name, description: repo.description, html_url: repo.html_url, - homepage: repo.homepage, + homepage: repo.website || repo.homepage, language: repo.language, - stargazers_count: repo.stargazers_count, - forks_count: repo.forks_count, - open_issues_count: repo.open_issues_count, + stargazers_count: repo.stars_count || repo.stargazers_count || 0, + forks_count: repo.forks_count || 0, + open_issues_count: repo.open_issues_count || 0, topics: repo.topics || [], - updated_at: repo.updated_at, - created_at: repo.created_at, + updated_at: repo.updated, + created_at: repo.created, })) - .slice(0, 10); // Limit to 10 repos + .slice(0, 10); } catch (error) { - console.error("Error fetching GitHub repos:", error.message); + console.error("Error fetching Gitea repos:", error.message); return []; } } diff --git a/_data/site.js b/_data/site.js index 7d3d342..a52b165 100644 --- a/_data/site.js +++ b/_data/site.js @@ -112,6 +112,13 @@ export default { }, }, + // Gitea configuration + gitea: { + url: process.env.GITEA_URL || "https://gitea.giersig.eu", + org: process.env.GITEA_ORG || "giersig.eu", + repos: (process.env.GITEA_REPOS || "indiekit-blog,indiekit-server").split(",").filter(Boolean), + }, + // Webmentions configuration webmentions: { domain: process.env.SITE_URL?.replace("https://", "").replace("http://", "") || "example.com", diff --git a/_includes/components/blog-sidebar.njk b/_includes/components/blog-sidebar.njk index d22ebc0..3630a2c 100644 --- a/_includes/components/blog-sidebar.njk +++ b/_includes/components/blog-sidebar.njk @@ -14,7 +14,7 @@ {# Resolve widget title #} {% if widget.type == "search" %}{% set widgetTitle = "Search" %} {% elif widget.type == "social-activity" %}{% set widgetTitle = "Social Activity" %} - {% elif widget.type == "github-repos" %}{% set widgetTitle = "GitHub" %} + {% elif widget.type == "github-repos" %}{% set widgetTitle = "Gitea" %} {% elif widget.type == "funkwhale" %}{% set widgetTitle = "Listening" %} {% elif widget.type == "recent-posts" %}{% set widgetTitle = "Recent Posts" %} {% elif widget.type == "blogroll" %}{% set widgetTitle = "Blogroll" %} diff --git a/_includes/components/homepage-sidebar.njk b/_includes/components/homepage-sidebar.njk index 9601cd5..c5fba9f 100644 --- a/_includes/components/homepage-sidebar.njk +++ b/_includes/components/homepage-sidebar.njk @@ -10,7 +10,7 @@ {# Resolve widget title #} {% if widget.type == "search" %}{% set widgetTitle = "Search" %} {% elif widget.type == "social-activity" %}{% set widgetTitle = "Social Activity" %} - {% elif widget.type == "github-repos" %}{% set widgetTitle = "GitHub" %} + {% elif widget.type == "github-repos" %}{% set widgetTitle = "Gitea" %} {% elif widget.type == "funkwhale" %}{% set widgetTitle = "Listening" %} {% elif widget.type == "recent-posts" %}{% set widgetTitle = "Recent Posts" %} {% elif widget.type == "blogroll" %}{% set widgetTitle = "Blogroll" %} diff --git a/_includes/components/sidebar.njk b/_includes/components/sidebar.njk index 78b02ff..d1d7c81 100644 --- a/_includes/components/sidebar.njk +++ b/_includes/components/sidebar.njk @@ -12,7 +12,7 @@ {# Resolve widget title #} {% if widget.type == "search" %}{% set widgetTitle = "Search" %} {% elif widget.type == "social-activity" %}{% set widgetTitle = "Social Activity" %} - {% elif widget.type == "github-repos" %}{% set widgetTitle = "GitHub" %} + {% elif widget.type == "github-repos" %}{% set widgetTitle = "Gitea" %} {% elif widget.type == "funkwhale" %}{% set widgetTitle = "Listening" %} {% elif widget.type == "recent-posts" %}{% set widgetTitle = "Recent Posts" %} {% elif widget.type == "blogroll" %}{% set widgetTitle = "Blogroll" %} @@ -206,7 +206,7 @@
diff --git a/_includes/components/widgets/github-repos.njk b/_includes/components/widgets/github-repos.njk index d8f5432..8e38d15 100644 --- a/_includes/components/widgets/github-repos.njk +++ b/_includes/components/widgets/github-repos.njk @@ -1,30 +1,20 @@ -{# GitHub Activity Widget - Tabbed Commits/Repos/Featured/PRs with live API data #} +{# Gitea Activity Widget - Tabbed Commits/Repos/Featured/PRs #} {% set ghFallbackCommits = githubActivity.commits if githubActivity and githubActivity.commits else [] %} {% set ghFallbackFeatured = githubActivity.featured if githubActivity and githubActivity.featured else [] %} {% set ghFallbackContributions = githubActivity.contributions if githubActivity and githubActivity.contributions else [] %} {% set ghFallbackRepos = githubRepos if githubRepos else [] %} -{% set id = homepageConfig.identity if (homepageConfig and homepageConfig.identity) else {} %} -{% set socialLinks = id.social if (id.social is defined) else site.social %} -{% set githubProfileUrl = "" %} -{% for link in socialLinks %} - {% if not githubProfileUrl and (link.icon == "github" or "github.com/" in link.url) %} - {% set githubProfileUrl = link.url %} - {% endif %} -{% endfor %} -{% if not githubProfileUrl and site.feeds.github %} - {% set githubProfileUrl = "https://github.com/" + site.feeds.github %} -{% endif %} -
+{% set giteaProfileUrl = site.gitea.url + "/" + site.gitea.org %} +

- GitHub + Gitea

{# Tab buttons — order: Commits, Repos, Featured, PRs #} -
+
{# Footer link #} - {% if githubProfileUrl %} - - View on GitHub + + View on Gitea - {% endif %}