diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md new file mode 100644 index 00000000..390148ce --- /dev/null +++ b/API_DOCUMENTATION.md @@ -0,0 +1,981 @@ +# dgrebb.com API Documentation + +## Table of Contents + +- [Overview](#overview) +- [Architecture](#architecture) +- [Backend APIs (Strapi CMS)](#backend-apis-strapi-cms) + - [Content Types](#content-types) + - [API Endpoints](#api-endpoints) +- [Frontend APIs & Components](#frontend-apis--components) + - [Core Utilities](#core-utilities) + - [API Integration](#api-integration) + - [UI Components](#ui-components) + - [Layout Components](#layout-components) + - [Content Components](#content-components) +- [Constants & Configuration](#constants--configuration) +- [Usage Examples](#usage-examples) + +## Overview + +This documentation covers the comprehensive API surface of dgrebb.com, a full-stack SvelteKit application with a Strapi CMS backend. The platform serves as a personal website featuring blog posts, portfolio content, and professional experience showcase. + +**Technology Stack:** +- **Frontend**: SvelteKit, JavaScript, Vite +- **Backend**: Strapi CMS v4.25.2, Node.js, PostgreSQL +- **Infrastructure**: AWS (ECS, ALB, CloudFront, RDS, S3) +- **Analytics**: Plausible Analytics + +## Architecture + +The application follows a static site generation (SSG) pattern with server-side rendering (SSR) capabilities: + +- **Frontend**: `/front/` - SvelteKit application +- **Backend**: `/back/` - Strapi headless CMS +- **Deployment**: Dockerized containers on AWS ECS + +## Backend APIs (Strapi CMS) + +### Content Types + +The Strapi backend defines several content types that power the frontend application: + +#### Posts (`/api/posts`) +Blog posts with rich content support. + +**Schema:** +```json +{ + "slug": "string (uid, required)", + "title": "string (required, unique)", + "hero": "media (image, required)", + "summary": "text", + "position": "enumeration (image position)", + "content": "dynamiczone (text, code, quote, animated-image, columns, image-carousel, html)", + "footnotes": "component (repeatable)", + "related": "relation (oneToMany posts)", + "categories": "relation (oneToMany categories)", + "seo": "component (shared.seo)" +} +``` + +#### Categories (`/api/categories`) +Post categorization system. + +**Schema:** +```json +{ + "name": "string (required, unique)", + "slug": "string (uid, required)", + "description": "richtext", + "seo": "component (shared.seo)" +} +``` + +#### CV (`/api/cv`) +Portfolio and professional experience data (single type). + +**Schema:** +```json +{ + "content": "component (shared.landing-page-content)", + "seo": "component (shared.seo)" +} +``` + +#### Additional Content Types +- **Awards** (`/api/awards`) - Professional recognition +- **Skills** (`/api/skills`) - Technical and professional skills +- **Projects** (`/api/projects`) - Portfolio projects +- **Experiences** (`/api/experiences`) - Work experience +- **Organizations** (`/api/organizations`) - Company/organization data +- **Industries** (`/api/industries`) - Industry classifications +- **Certifications** (`/api/certifications`) - Professional certifications +- **Classifications** (`/api/classifications`) - Skill/experience categories + +### API Endpoints + +All Strapi endpoints follow REST conventions: + +``` +GET /api/{content-type} # List all items +GET /api/{content-type}/:id # Get specific item +POST /api/{content-type} # Create new item (admin only) +PUT /api/{content-type}/:id # Update item (admin only) +DELETE /api/{content-type}/:id # Delete item (admin only) +``` + +**Query Parameters:** +- `populate=*` - Include related content +- `filters[field][$eq]=value` - Filter by field value +- `sort=field:asc|desc` - Sort results +- `pagination[page]=1&pagination[pageSize]=10` - Pagination + +## Frontend APIs & Components + +### Core Utilities + +Located in `/front/src/lib/_utils/index.js` + +#### Theme Management + +```javascript +/** + * Manages theme preference in local storage. + * @param {boolean} dark - Theme preference (optional). + * @returns {boolean | undefined} - Theme preference if retrieved, otherwise false. + */ +export const themeStorage = (dark) => { /* ... */ } + +/** + * Get theme name based on preference + * @param {boolean} preference - Dark theme preference + * @returns {Promise} - Theme class name + */ +export const themeName = async function (preference) => { /* ... */ } +``` + +**Usage:** +```javascript +import { themeStorage, themeName } from '$lib/_utils'; + +// Save theme preference +themeStorage(true); // Save dark mode preference + +// Retrieve theme preference +const isDark = themeStorage(); // Returns stored preference + +// Get CSS class name +const themeClass = await themeName(isDark); // Returns 'dark-theme' or 'light-theme' +``` + +#### DOM Utilities + +```javascript +/** + * Scroll to top of page + * @param {Event} e - Event object + */ +export const scrollTop = function (e) => { /* ... */ } + +/** + * Check if element is outside viewport + * @param {HTMLElement} element - The HTML element to check + * @returns {boolean} - True if outside viewport + */ +export const isElementOutsideViewport = (element) => { /* ... */ } + +/** + * Check if user prefers reduced motion + * @returns {boolean} - True if reduced motion preferred + */ +export const motionless = function () => { /* ... */ } +``` + +#### Text & Color Utilities + +```javascript +/** + * Copy text to clipboard + * @param {Event} e - Click event containing target element + * @returns {Promise} - Clipboard write operation + */ +export async function copyText(e) => { /* ... */ } + +/** + * Extract domain from URL without www prefix + * @param {string} url - URL to process + * @returns {string|null} - Domain or null if invalid + */ +export const extractDomainWithoutWWW = function (url) => { /* ... */ } + +/** + * Get high contrast color (black/white) for given background + * @param {string} hexColor - Background color in hex format + * @returns {string} - '#000000' or '#FFFFFF' + */ +export const getHighContrastHexColor = function (hexColor) => { /* ... */ } + +/** + * Convert hex color to RGB string + * @param {string} hex - Hex color (with or without #) + * @returns {string|null} - "r, g, b" format or null if invalid + */ +export const getRGBFromHex = function (hex) => { /* ... */ } +``` + +### API Integration + +Located in `/front/src/lib/api/` + +#### Content API (`content.js`) + +```javascript +/** + * Fetches content from specified endpoint + * @param {string} endpoint - API endpoint URL + * @returns {Promise} - Content data or error object + */ +export default async function content(endpoint) => { /* ... */ } +``` + +**Usage:** +```javascript +import content from '$lib/api'; + +// Fetch all posts +const posts = await content('/api/posts?populate=*'); + +// Fetch specific post +const post = await content('/api/posts/1?populate=*'); + +// Handle errors +const result = await content('/api/posts'); +if (result.error) { + console.error('Failed to fetch:', result.error); +} +``` + +#### Category API (`category.js`) + +```javascript +/** + * Fetch category data from endpoint + * @param {string} endpoint - Category API endpoint + * @returns {Promise} - First category object or error + */ +export async function categoryAPI(endpoint) => { /* ... */ } +``` + +**Usage:** +```javascript +import { categoryAPI } from '$lib/api'; + +const category = await categoryAPI('/api/categories?filters[slug][$eq]=tech'); +``` + +### UI Components + +#### General Components (`/front/src/lib/components/general/`) + +##### Image Component +```svelte + + + + +``` + +**Usage:** +```svelte + + +Hero image +``` + +##### Links Component +```svelte + + +``` + +**Usage:** +```svelte + + + +``` + +##### Loader Components +```svelte + + + + + +``` + +##### Meta Components + +```svelte + + + + + +``` + +**Usage:** +```svelte + + + +``` + +##### Navigation Components + +```svelte + + + + + +``` + +##### Transition Components + +```svelte + + + + + + + + +``` + +#### Content Components + +##### Posts Components (`/front/src/lib/components/posts/`) + +```svelte + + + + + + + + + + + + + + +``` + +**Usage:** +```svelte + + + + + + + +``` + +##### CV Components (`/front/src/lib/components/cv/`) + +```svelte + + + + + + + + + + + +``` + +### Layout Components + +#### Page Navigation (`/front/src/lib/components/general/PageNav/`) + +The PageNav component provides pagination functionality: + +```svelte + + + + +``` + +### Constants & Configuration + +#### Application Constants (`/front/src/lib/CONSTANTS.js`) + +```javascript +export const URIS = { + cdn: 'https://s.dgrebb.com', // CDN endpoint + www: 'https://www.dgrebb.com', // Production URL + stg: 'https://stg.dgrebb.com', // Staging URL + math: 'https://p.dgrebb.com', // Analytics URL +}; + +export const PATHS = { + one: { + post: '/post', // Single post path + category: '/posts/category', // Category path + experience: '/cv/experience', // Experience path + // ... other single item paths + }, + many: { + posts: '/posts', // Posts listing + categories: '/posts/category', // Categories listing + experiences: '/cv/experiences', // Experiences listing + // ... other collection paths + }, + landing: { + home: '/', // Homepage + cv: '/cv', // CV page + privacy: '/privacy', // Privacy policy + rss: '/RSS.xml', // RSS feed + fof: '/404', // 404 page + }, +}; +``` + +**Usage:** +```javascript +import { URIS, PATHS } from '$lib/CONSTANTS'; + +// Build URLs +const cdnImage = `${URIS.cdn}/images/hero.jpg`; +const postUrl = `${PATHS.one.post}/${slug}`; +``` + +#### API Constants (`/front/src/lib/api/CONSTANTS.js`) + +```javascript +export const OPTIONS = { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + // Add authentication headers as needed +}; +``` + +## Usage Examples + +### Fetching and Displaying Posts + +```svelte + + + + + +
+

Latest Posts

+ +
+``` + +```javascript +// routes/posts/+page.server.js +import content from '$lib/api'; + +export async function load() { + const posts = await content('/api/posts?populate=*&sort=publishedAt:desc'); + + return { + posts: posts.error ? [] : posts + }; +} +``` + +### Creating a Custom Component + +```svelte + + + +
+ {#if image} +
+ {image.alt +
+ {/if} + +
+

{title}

+

{description}

+ + {#if links.length > 0} + + {/if} +
+
+ + +``` + +### Advanced API Usage with Error Handling + +```javascript +// lib/api/enhanced-content.js +import content from './content.js'; + +/** + * Enhanced content fetcher with caching and retry logic + * @param {string} endpoint - API endpoint + * @param {Object} options - Configuration options + * @returns {Promise} - Enhanced response with metadata + */ +export async function enhancedContent(endpoint, options = {}) { + const { + cache = true, + retries = 3, + timeout = 5000, + transform = null + } = options; + + // Cache key for client-side caching + const cacheKey = `content_${btoa(endpoint)}`; + + // Check cache first + if (cache && typeof window !== 'undefined') { + const cached = sessionStorage.getItem(cacheKey); + if (cached) { + try { + const data = JSON.parse(cached); + if (Date.now() - data.timestamp < 300000) { // 5 minutes + return { data: data.content, cached: true }; + } + } catch (e) { + sessionStorage.removeItem(cacheKey); + } + } + } + + // Retry logic + for (let attempt = 1; attempt <= retries; attempt++) { + try { + const result = await Promise.race([ + content(endpoint), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), timeout) + ) + ]); + + if (result.error) { + if (attempt === retries) { + throw new Error(result.error.message || 'API Error'); + } + continue; + } + + // Transform data if function provided + const transformedData = transform ? transform(result) : result; + + // Cache successful result + if (cache && typeof window !== 'undefined') { + sessionStorage.setItem(cacheKey, JSON.stringify({ + content: transformedData, + timestamp: Date.now() + })); + } + + return { + data: transformedData, + cached: false, + attempt + }; + + } catch (error) { + if (attempt === retries) { + throw error; + } + // Wait before retry + await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); + } + } +} +``` + +**Usage:** +```javascript +import { enhancedContent } from '$lib/api/enhanced-content.js'; + +// Basic usage +const { data: posts } = await enhancedContent('/api/posts?populate=*'); + +// With transformation +const { data: postsWithUrls } = await enhancedContent('/api/posts?populate=*', { + transform: (posts) => posts.map(post => ({ + ...post, + url: `/post/${post.slug}` + })) +}); + +// With custom options +const { data: categories, cached } = await enhancedContent('/api/categories', { + cache: true, + retries: 5, + timeout: 10000 +}); +``` + +### Form Handling Example + +```svelte + + + +
+
+ + + {#if errors.name} + {errors.name} + {/if} +
+ +
+ + + {#if errors.email} + {errors.email} + {/if} +
+ +
+ + + {#if errors.message} + {errors.message} + {/if} +
+ + + + {#if isSuccess} +
+ Thank you! Your message has been sent. +
+ {/if} +
+ + +``` + +This comprehensive documentation covers all major public APIs, components, and functions in the dgrebb.com codebase. The examples demonstrate real-world usage patterns and best practices for extending the application. \ No newline at end of file