# Architecture **Analysis Date:** 2026-02-15 ## Pattern Overview **Overall:** Monolithic multi-tier architecture with separated frontend and backend services. **Key Characteristics:** - Frontend: Single-Page Application (SPA) with React + React Router - Backend: Express.js REST API with direct database queries - Database: PostgreSQL with relational schema - State Management: React Context API for authentication, local component state for page-level data - Communication: HTTP/JSON via Fetch API with Bearer token authentication - Deployment: Containerized (Docker) with Traefik reverse proxy routing ## Layers **Presentation Layer (Frontend):** - Purpose: Render UI, handle user interactions, manage local state and navigation - Location: `/workspace/gravl/frontend/src/` - Contains: React pages, components, context providers, CSS styling - Depends on: React, React Router, AuthContext, backend API endpoints - Used by: Browser clients **Application/Page Layer (Frontend):** - Purpose: Manage view logic, fetch data, orchestrate navigation between different views - Location: `/workspace/gravl/frontend/src/pages/` - Contains: Full page components (Dashboard, WorkoutPage, ProfilePage, ProgressPage, LoginPage, RegisterPage, OnboardingWizard, WorkoutSelectPage) - Depends on: AuthContext, Icon components, API calls via fetch - Used by: App.jsx routing logic **Context Layer (Frontend):** - Purpose: Provide global authentication state and user session management - Location: `/workspace/gravl/frontend/src/context/AuthContext.jsx` - Contains: Auth state, login/register/logout functions, token management, localStorage integration - Depends on: React hooks, backend authentication endpoints - Used by: All protected pages and components **API/REST Layer (Backend):** - Purpose: Handle HTTP requests, validate input, manage authentication, route requests to data layer - Location: `/workspace/gravl/backend/src/index.js` - Contains: Express routes for auth, user profile, programs, exercises, logs, progression - Depends on: PostgreSQL connection, JWT verification, bcrypt password hashing - Used by: Frontend via HTTP requests **Data Layer (Backend):** - Purpose: Execute queries against PostgreSQL database - Location: Database queries within `/workspace/gravl/backend/src/index.js` using pg Pool - Contains: User management, program/day/exercise definitions, workout logs, measurements, strength records - Depends on: PostgreSQL driver (pg) - Used by: API layer for all data operations **Database Layer:** - Purpose: Persist application data - Location: `/workspace/gravl/db/init.sql` (schema definition) - Contains: 7 tables (users, programs, program_days, exercises, program_exercises, workout_logs, user_measurements, user_strength) - Depends on: PostgreSQL engine - Used by: Backend data layer ## Data Flow **User Registration/Login Flow:** 1. User enters credentials on RegisterPage or LoginPage 2. Page calls `useAuth().register()` or `useAuth().login()` from AuthContext 3. AuthContext makes POST to `/api/auth/register` or `/api/auth/login` 4. Backend validates credentials (register: email uniqueness + hash password; login: password verification) 5. Backend returns JWT token and user object 6. AuthContext stores token in localStorage and sets user state 7. Navigation redirects to `/onboarding` (incomplete) or `/` (complete) **Onboarding Flow:** 1. User completes OnboardingWizard with profile data (gender, age, experience, goal, measurements, strength) 2. Wizard calls `useAuth().updateProfile()` with profile data 3. Backend updates users table and related measurement/strength tables 4. Sets `onboarding_complete = true` 5. User navigated to Dashboard **Workout/Exercise Flow:** 1. Dashboard displays program days and selected day's workout 2. User clicks workout day, `onStartWorkout()` called 3. App.jsx calls `fetchProgram()` to load program with all days/exercises 4. App.jsx calls `fetchLogs()` to fetch existing workout logs for that day 5. WorkoutPage displayed with exercises and weight/rep input fields 6. User enters weight/reps and clicks "Log Set" 7. `logSet()` calls POST `/api/logs` with exercise_id, weight, reps, date, set_number 8. Backend checks if log exists for that set (update) or creates new (insert) 9. Response updates local logs state 10. WorkoutPage re-renders with updated data **Progression Calculation Flow:** 1. WorkoutPage calls `fetchProgression()` for each exercise 2. Backend fetches last workout for that exercise (last 10 logs, completed only) 3. Analyzes if all sets hit max_reps 4. Returns suggestedWeight (same weight or +2.5kg if maxed out) 5. Frontend displays suggestion in workout interface **Profile/Measurements Flow:** 1. User navigates to ProfilePage 2. Page calls parallel fetches: `/api/user/profile`, `/api/user/measurements`, `/api/user/strength` 3. Backend joins latest measurements and strength records with user profile 4. Page displays current profile and can add new measurements or strength records 5. User saves changes → updates user profile state in AuthContext **State Management:** - **Global state:** User session, authentication token (AuthContext in localStorage) - **Page-level state:** Program, logs, current view, selected day (App.jsx state) - **Component-level state:** Form inputs, editing mode, expanded sections (individual page components) - **No shared state management library:** Direct React Context + local useState ## Key Abstractions **AuthContext:** - Purpose: Centralized authentication and user session management - Examples: `useAuth()` hook returns { user, token, loading, register, login, logout, updateProfile, refreshProfile } - Pattern: React Context + custom hook for easy access from any component **Page Components:** - Purpose: Encapsulate view logic, form handling, and local data fetching - Examples: `Dashboard.jsx`, `WorkoutPage.jsx`, `ProfilePage.jsx` - Pattern: Functional components with useState/useEffect, direct API calls via fetch **Program/Exercise Model:** - Purpose: Represent training structure in database and API - Structure: Program > Days > Exercises (program_exercises join table) > Logs (user workout records) - Pattern: Nested JSON responses from `/api/programs/:id` endpoint **Workout Log:** - Purpose: Record individual set performance (weight, reps, completion status) - Examples: `workout_logs` table with user_id, program_exercise_id, date, set_number, weight, reps, completed - Pattern: Upsert logic (update if exists, insert if new) ## Entry Points **Frontend Entry:** - Location: `frontend/index.html` → `src/main.jsx` → `src/App.jsx` - Triggers: Browser loads gravl.homelab.local - Responsibilities: 1. Bootstrap React app with BrowserRouter and AuthProvider 2. Define route structure (auth routes vs. protected routes) 3. Initialize token from localStorage and verify session 4. Render main App component **Backend Entry:** - Location: `backend/src/index.js` - Triggers: Docker container startup (`npm start` → `node src/index.js`) - Responsibilities: 1. Initialize Express app and PostgreSQL connection pool 2. Mount CORS and JSON middleware 3. Define all API routes with request/response handling 4. Listen on port 3001 5. Database queries executed inline within route handlers **Auth-Protected Routes:** - ProtectedRoute wrapper checks user existence and onboarding status - Redirects to `/login` if unauthenticated - Redirects to `/onboarding` if authenticated but onboarding incomplete - Routes: `/`, `/profile`, `/progress`, `/select-workout`, `/workout` **Auth Routes:** - AuthRoute wrapper redirects to `/` or `/onboarding` if already authenticated - Routes: `/login`, `/register` ## Error Handling **Strategy:** Try-catch in Express routes returns JSON errors; frontend logs errors and may show error UI **Patterns:** - Backend: Catch database/auth errors, return 400/401/500 with JSON error message - Frontend: Catch fetch errors in async functions, log to console, optionally show in component error state - Validation: Frontend form validation (required, minLength); backend re-validates email uniqueness - Auth failures: Return 401 Unauthorized, AuthContext logs user out - Database errors: Return 500 with generic message (details in server logs only) ## Cross-Cutting Concerns **Logging:** - Backend: `console.error()` for exceptions; logs visible in Docker container stdout - Frontend: `console.error()` for network failures and state issues **Validation:** - Frontend: HTML5 form validation (type="email", minLength, required) - Backend: Email lowercase normalization, null coercion for numeric fields, JWT signature verification **Authentication:** - Method: JWT Bearer token in Authorization header - Token storage: localStorage on browser - Token validation: `authMiddleware` function checks header and verifies signature - Token lifetime: 30 days expiration - Session management: AuthContext refresh on mount, logout clears localStorage and state **CORS:** - Enabled globally with `cors()` middleware on all routes - Frontend proxy configured in Vite for `/api` calls during development - Docker network configured for service-to-service communication **Data Integrity:** - Foreign key constraints in database schema (ON DELETE CASCADE) - Unique email constraint in users table - Indexes on frequently queried columns (user_id, date, program_exercise_id) --- *Architecture analysis: 2026-02-15*