docs: map existing codebase
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
# 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*
|
||||
Reference in New Issue
Block a user