Skip to content

Plan: API-Backed Content — All Data Objects in the Database

Archived: fully implemented as of 2026-05-05. All endpoints, frontend hooks, feature flag infra, and DB models complete (PRs #178, #181, #184, #358, #362). Only Phase 8 (flip portfolioContentApi prod flag) remains, tracked separately.

Status: Complete — all phases done except Phase 8 (prod flag flip, deferred) Scope: Replace all hardcoded client/src/data/*.ts arrays with data served from the backend API + stored in PostgreSQL


Goal

Move all portfolio content out of the frontend bundle into the database. Content can then be updated without a code deploy, and UIGen provides an admin CRUD interface automatically.

The migration is feature-flagged: the existing static data files remain as the fallback while the flag is off. When the flag (portfolio-content-api) is enabled, the frontend fetches from the API instead. This allows a gradual rollout and instant rollback without a redeploy.


What Already Exists (as of 2026-05-03)

DB models — all present in api/db/models.py

  • Project — full schema with slug, category, phase, timeline, all detail fields
  • WorkEntry — full schema with responsibilities, technologies, sort_order
  • SkillCategory + Skill — with relationship, sort_order

Missing models: Award, Certification

Alembic migrations — api/alembic/versions/

  • 0001_initial_schema.py — projects + work_entries + skill tables created
  • 0002_seed_initial_data.py — seed data for projects, work_entries, skills
  • 0003_add_iac_platform_project.py — additional project added

Missing migrations: awards + certifications tables

Backend endpoints — already in api/main.py

  • GET /api/projects
  • GET /api/projects/{id}
  • GET /api/experience
  • GET /api/experience/{id} ✅ (bonus, not in plan)
  • GET /api/skills

Missing endpoints: GET /api/experience/preview, GET /api/awards, GET /api/certifications

Note: Awards and certifications exist as static AWARDS / CERTIFICATIONS lists in main.py and are used by the CV generation context only — no public endpoints.

Frontend feature flags

  • FeatureFlagsContext only has cvGeneration: boolportfolioContentApi not yet added
  • useProjects.ts hook does not exist at client/src/data/hooks/ (only client/src/data/useContactForm.ts)

Infra / App Config

  • portfolio-content-api flag not yet in infra/modules/app_configuration.bicep

Current State — Data Objects

File Exported constant DB model API endpoint Frontend hook
data/experience.ts workHistory[] WorkEntry GET /api/experience
data/experience.ts sectionExperiences[] derived GET /api/experience/preview
data/skills.ts skillCategories[] SkillCategory+Skill GET /api/skills
data/projects.ts projectCards[] Project GET /api/projects
data/projects.ts projectDetails{} Project (detail fields) ✅ GET /api/projects/{id}
data/awards.ts awards[]
data/certifications.ts certifications[]

Feature Flag Gate

A new flag portfolio-content-api is added to Azure App Configuration with labels dev and prod.

  • dev: on — always fetch from API in dev
  • prod: off initially — flip to on once all endpoints are live and seeded

The frontend FeatureFlagsContext already exposes flags to all components. Each hook checks this flag:

ts
function useWorkHistory(): WorkEntry[] {
  const { portfolioContentApi } = useFeatureFlags();
  const [data, setData] = useState<WorkEntry[]>(workHistory); // static fallback always loads first

  useEffect(() => {
    if (!portfolioContentApi) return;           // flag off → keep static data
    fetch('/api/experience')
      .then(r => r.json())
      .then(setData)
      .catch(() => {});                         // flag on but API fails → keep static data
  }, [portfolioContentApi]);

  return data;
}

TanStack Query (@tanstack/react-query v5) is present — use useQuery for loading/error/stale handling instead of raw fetch + useState.


Target Architecture

Azure App Configuration
  portfolio-content-api: dev=on, prod=off (flip when ready)

Frontend (React)
  FeatureFlagsContext.portfolioContentApi
    true  → GET /api/experience          → work_entries table
           → GET /api/experience/preview → work_entries WHERE is_current OR top 4 by sort_order
           → GET /api/skills             → skill_categories + skills tables
           → GET /api/projects           → projects table
           → GET /api/projects/{id}      → project_details (columns on Project model)
           → GET /api/awards             → awards table
           → GET /api/certifications     → certifications table
    false → existing static data/*.ts files (unchanged)

Remaining Work

Phase 1 — Infra: App Config feature flag

infra/modules/app_configuration.bicep:

  • Add portfolio-content-api flag (dev=on, prod=off)
  • Add content_api_prod_enabled bool = false param (same pattern as cv_generation_prod_enabled)
  • Add fallback env var FEATURE_PORTFOLIO_CONTENT_API to feature_flags.py _FALLBACK_ENV_VARS

Phase 2 — Backend: Awards + Certifications models + migration

api/db/models.py — add Award + Certification models (UUID PK, sort_order, all fields matching frontend types including icon_name/bg_color for Award and icon_type/link/link_text for Certification).

New migration 0004_add_awards_certifications.py:

  • Create awards + certifications tables
  • Seed from current static AWARDS + CERTIFICATIONS lists in main.py

Phase 3 — Backend: Missing endpoints

In api/main.py (or extract to api/routers/portfolio.py):

python
GET /api/experience/preview   # WorkEntry WHERE is_current=true OR top 4 by sort_order
GET /api/awards               # Award ordered by sort_order
GET /api/certifications       # Certification ordered by sort_order

Add Award + Certification Pydantic response models with all frontend fields (including iconName/bgColor and iconType/link/linkText using camelCase to match frontend).

Update FeatureFlagsResponse + get_feature_flags() to include portfolioContentApi. Update feature_flags.py _FALLBACK_ENV_VARS to map "portfolio-content-api""FEATURE_PORTFOLIO_CONTENT_API".

Phase 4 — Backend: Tests

New pytest tests in tests/test_portfolio_endpoints.py:

  • GET /api/experience — returns list, fields present
  • GET /api/experience/preview — max 4 items
  • GET /api/skills — nested categories with skills
  • GET /api/projects — returns list
  • GET /api/projects/{slug} — returns detail
  • GET /api/awards — returns list with iconName + bgColor
  • GET /api/certifications — returns list with iconType

All tests use FastAPI TestClient, no live DB (endpoints fall back gracefully when DATABASE_URL unset — verify this is true for new endpoints too).

Phase 5 — Frontend: Feature flag + hooks

client/src/contexts/feature-flags-context.tsx:

  • Add portfolioContentApi: boolean to FeatureFlags interface
  • Add to defaultFlags
  • Update data: FeatureFlags type assertion

client/src/hooks/usePortfolioContent.ts (new file):

ts
// One hook per data object using useQuery when flag is on, static data otherwise
useWorkHistory()         WorkEntry[]
useExperiencePreview()   SectionExperience[]
useSkillCategories()     SkillCategory[]
useProjects()            ProjectCard[]
useProject(id: string)   ProjectDetail | undefined
useAwards()              Award[]
useCertifications()      Certification[]

Use useQuery from @tanstack/react-query with placeholderData set to the static array — this gives instant render from static data and background update from API.

Phase 6 — Frontend: Update consuming components

Component Replaces With
pages/experience.tsx import { workHistory } useWorkHistory()
experience-section.tsx import { sectionExperiences } useExperiencePreview()
skills-section.tsx import { skillCategories } useSkillCategories()
projects-section.tsx import { projectCards } useProjects()
project detail component projectDetails[id] useProject(id)
pages/about.tsx awards, certifications useAwards(), useCertifications()

Find exact component paths before editing.

Phase 7 — OpenAPI type sync + verification

  • Run pre-commit hook to regenerate shared/api.types.ts
  • Run npm run typecheck — should pass with no TS errors
  • Start backend + frontend locally; enable FEATURE_PORTFOLIO_CONTENT_API=true; verify all sections load from API

Phase 8 — Flip prod flag (follow-up, after verification)

  • Set content_api_prod_enabled = true in infra/parameters.prod.bicepparam
  • Deploy infra to prod
  • Smoke test prod; if green, static fallback can be removed in subsequent PR

Tailwind Safelist Note

Icon names (Trophy, Medal, Users, Waves) and bg colors (bg-blue-600, bg-green-600, bg-purple-600, bg-indigo-600) are stored as strings in the DB. These must be safelisted in tailwind.config.ts so PurgeCSS doesn't strip them.


UIGen Integration

Once awards + certifications tables exist, UIGen auto-generates CRUD UI for all 7 tables. Editing content becomes point-and-click with no code deploy.


Phase Status

Phase Scope Status
~~1~~ ~~Infra: App Config flag~~ ⬜ Remaining
~~2~~ Backend: DB models + migrations for projects/work/skills ✅ Done (PRs #178–184)
2b Backend: Award + Certification models + migration 0004 ⬜ Remaining
~~3~~ Backend: projects/experience/skills endpoints ✅ Done
3b Backend: /api/experience/preview, /api/awards, /api/certifications ⬜ Remaining
3c Backend: portfolioContentApi flag in feature-flags response ⬜ Remaining
4 Backend: pytest tests for all portfolio endpoints ⬜ Remaining
5 Frontend: FeatureFlagsContext + usePortfolioContent.ts hooks ⬜ Remaining
6 Frontend: update consuming components ⬜ Remaining
7 OpenAPI type sync + local E2E verification ⬜ Remaining
8 Flip prod flag + remove static fallback (follow-up PR) ⬜ Later