Documentation Index
Fetch the complete documentation index at: https://mintlify.com/santiagonieto09/portafolio/llms.txt
Use this file to discover all available pages before exploring further.
src/infrastructure/github/github-api.server.ts is the sole boundary between the portfolio and the GitHub REST API. Every outbound request to api.github.com, every piece of raw API data, and every mapping to domain types lives in this one server-only file. Nothing outside the infrastructure layer ever sees a raw GitHubUser or GitHubRepo — only the mapped domain types cross the layer boundary.
HTTP client
The internalget<T>(path, attempts) helper is the single entry point for all GitHub REST calls.
Base URL: https://api.github.com
Authentication: The headers() function reads GITHUB_TOKEN (falling back to GITHUB_API_KEY) from the process environment. When either is set, every request carries Authorization: Bearer <token>, raising the rate limit from 60 to 5 000 requests per hour. When neither is set the request is sent unauthenticated.
Required headers sent on every request:
| Header | Value |
|---|---|
Accept | application/vnd.github+json |
X-GitHub-Api-Version | 2022-11-28 |
User-Agent | portfolio-site |
get retries up to attempts times (default 3). Retryable conditions are:
- HTTP 403 — often a secondary rate-limit response rather than an auth error.
- HTTP 429 — explicit rate limit exceeded.
- HTTP 5xx — transient GitHub server error.
- Network exception — connection reset, DNS failure, etc.
500 * 2 ** attempt ms sleep after each failed attempt. With the default of attempts = 3 there are at most three tries, separated by sleeps of 500 ms and 1 000 ms. Any other 4xx response (404, 422, etc.) is treated as a permanent client error and returns null immediately without retrying.
Parallel fetch strategy
fetchPortfolio(username) fires four API requests simultaneously to minimise total latency:
- Repos: fetches up to 100 repositories sorted by last-updated date. Forked repositories are filtered out before any further processing.
- Events: up to 30 public events are fetched; the first 8 are mapped to
ActivityItemrecords. - Social accounts: supplemental links (LinkedIn, Instagram, etc.) merged with the profile’s
blogandtwitter_usernamefields.
mapLimit helper runs at most CONCURRENCY = 3 in-flight requests at once, which avoids triggering GitHub’s secondary rate limits that kick in when many parallel requests hit the same API resource in quick succession.
Repository enrichment
buildRepository(raw) enriches each raw GitHubRepo record with two additional API calls:
attempts = 1 (fail-fast). Because up to 100 repositories are processed, a slow retry chain on each would make the total fetch time unacceptably long. Instead, any failure gracefully degrades:
- Languages: falls back to
{ [raw.language]: 1 }using the primary language field from the list endpoint, or an empty object if the primary language is alsonull. - Release: set to
nullon failure; the UI simply omits the release badge for that repository.
detectTechnologies() (from the domain layer) infers the technology stack from the repository name, description, topics, language list, and homepage URL, returning an array of technology slugs (e.g. "react", "spring-boot", "docker").
The fully enriched record is mapped to the domain Repository type — all raw GitHub field names (stargazers_count, html_url, etc.) are normalised to camelCase domain names (stars, htmlUrl, etc.) at this boundary.
Degraded mode
The portfolio is designed to render correctly even when GitHub returns no data. The fallback chain infetchPortfolio is:
- Cache hit — fresh snapshot returned immediately, no GitHub calls needed.
- Successful fetch — user data received; repositories, activity, and socials are mapped and cached.
- No user data (e.g. rate limit exceeded on the
/users/{username}call):- Calls
readSnapshotOrStale()— returns the last known snapshot from memory even if its 7-day TTL has expired. - If no stale snapshot exists, calls
degradedSnapshot(username)which returns a structurally validPortfolioSnapshotwithdegraded: true, an empty repository list, and a minimal profile built from the username alone.
- Calls
degraded flag to optionally display a banner, but no error boundary is triggered and the page always produces valid HTML.
URL sanitisation
Social links and repository homepages pass throughsafeExternalUrl() before being stored in the domain types. The function:
- Detects bare domains (no scheme) and prepends
https://. - Parses the result with the
URLconstructor to validate structure. - Checks the protocol against an allowlist: only
http:andhttps:are permitted. - Returns
nullfor any URL that fails parsing or uses a disallowed scheme (javascript:,data:,ftp:, etc.).