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*
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
# Codebase Concerns
|
||||||
|
|
||||||
|
**Analysis Date:** 2026-02-15
|
||||||
|
|
||||||
|
## Tech Debt
|
||||||
|
|
||||||
|
**Hardcoded Program ID in Backend:**
|
||||||
|
- Issue: Multiple API endpoints hardcode `program_id = 1` in queries, preventing multi-program support
|
||||||
|
- Files: `backend/src/index.js` (lines 27, 198, 386, 410)
|
||||||
|
- Impact: Cannot support multiple training programs; system is locked to single PPL program. Future features requiring program selection will require significant refactoring
|
||||||
|
- Fix approach: Add `program_id` parameter to endpoints; refactor to accept program ID from request or user preferences
|
||||||
|
|
||||||
|
**Hardcoded User ID Default:**
|
||||||
|
- Issue: Backend defaults to `user_id = 1` when not provided; frontend also uses fallback `user?.id || 1`
|
||||||
|
- Files: `backend/src/index.js` (line 290), `frontend/src/App.jsx` (line 21), `frontend/src/pages/ProfilePage.jsx` (lines 25-27, 48)
|
||||||
|
- Impact: Multi-user isolation broken; all users can see/modify each other's data if API auth fails. Critical security concern
|
||||||
|
- Fix approach: Remove all fallback user IDs; enforce auth token verification; validate user ownership on all endpoints
|
||||||
|
|
||||||
|
**Single Backend File Architecture:**
|
||||||
|
- Issue: All 425 lines of API logic in one file (`backend/src/index.js`); no separation into routes, controllers, middleware
|
||||||
|
- Files: `backend/src/index.js`
|
||||||
|
- Impact: Difficult to maintain, test, or extend. Mixed concerns (auth, database, business logic) in same file. No clear patterns for new endpoints
|
||||||
|
- Fix approach: Refactor into: `routes/`, `controllers/`, `middleware/`, `services/` directories
|
||||||
|
|
||||||
|
**No Request Validation:**
|
||||||
|
- Issue: No input validation on any API endpoints; accepts any data and passes to database
|
||||||
|
- Files: `backend/src/index.js` (all POST/PUT routes: lines 35-50, 103-118, 121-136, 153-168, 299-329)
|
||||||
|
- Impact: SQL injection risk (mitigated by parameterized queries but semantic validation missing), malformed data in database, inconsistent state
|
||||||
|
- Fix approach: Add validation library (e.g., joi, zod); validate types, ranges, required fields before database operations
|
||||||
|
|
||||||
|
**Weak Default JWT Secret:**
|
||||||
|
- Issue: JWT secret defaults to plain string `'gravl-secret-key-change-in-production'` if env var not set
|
||||||
|
- Files: `backend/src/index.js` (line 9)
|
||||||
|
- Impact: If deployment forgets to set JWT_SECRET env var, all tokens can be forged. Auth completely broken in that scenario
|
||||||
|
- Fix approach: Require JWT_SECRET as mandatory env var; fail at startup if not set; remove default
|
||||||
|
|
||||||
|
**Exposed Database Password in Docker Compose:**
|
||||||
|
- Issue: Database password hardcoded in plaintext in `docker-compose.yml`
|
||||||
|
- Files: `docker-compose.yml` (line 12: `DB_PASSWORD=homelab_postgres_2026`)
|
||||||
|
- Impact: Secret visible in git history and version control. Deployed as plain text to running containers
|
||||||
|
- Fix approach: Use `.env` file (gitignored) with env var substitution; never commit secrets
|
||||||
|
|
||||||
|
**Hardcoded Database Connection Defaults:**
|
||||||
|
- Issue: Database credentials have weak defaults if env vars missing
|
||||||
|
- Files: `backend/src/index.js` (lines 11-17)
|
||||||
|
- Impact: If env vars not set, connects with default user/password/database
|
||||||
|
- Fix approach: Require critical env vars (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME) as mandatory at startup
|
||||||
|
|
||||||
|
**No Error Handling for Database Failures:**
|
||||||
|
- Issue: Database errors logged to console but no graceful degradation or retry logic
|
||||||
|
- Files: `backend/src/index.js` (lines 46-50, 64-67, 97-100, 114-117, 132-135, 164-167, 189-191, 227-230, 245-248, 275-278, 292-295, 325-328, 379-382, 416-419)
|
||||||
|
- Impact: Client gets generic "Database error" message; no way to debug; connection pool exhaustion not handled
|
||||||
|
- Fix approach: Add structured error logging; implement connection retry logic; differentiate error messages (auth vs DB vs validation)
|
||||||
|
|
||||||
|
**Vague Error Messages:**
|
||||||
|
- Issue: Many endpoints return generic `{ error: 'Server error' }` without details
|
||||||
|
- Files: `backend/src/index.js` (lines 49, 66, 116, 134, 166, 191, 229, 248, 278, 295, 328, 381, 418)
|
||||||
|
- Impact: Frontend cannot distinguish between different failure modes; users see unhelpful messages; debugging impossible
|
||||||
|
- Fix approach: Use structured error codes (e.g., `ERR_EMAIL_EXISTS`, `ERR_INVALID_CREDENTIALS`, `ERR_DB_UNAVAILABLE`)
|
||||||
|
|
||||||
|
## Known Bugs
|
||||||
|
|
||||||
|
**User ID Fallback Breaks Multi-User:**
|
||||||
|
- Symptoms: Any endpoint called without proper auth gets user ID 1; all data accessible to wrong users
|
||||||
|
- Files: `backend/src/index.js` (line 290: `user_id || 1`)
|
||||||
|
- Trigger: Call `/api/logs/last/...?program_exercise_id=X` without user_id parameter, or auth fails silently
|
||||||
|
- Workaround: Frontend always provides user ID; but if auth token expires mid-session, falls back to user 1
|
||||||
|
|
||||||
|
**Progression Calculation Assumes User Context:**
|
||||||
|
- Symptoms: `/api/progression/:id` endpoint without auth header returns data for user_id=1, not current user
|
||||||
|
- Files: `backend/src/index.js` (line 334: `user_id || 1`)
|
||||||
|
- Trigger: Make unauthenticated request to progression endpoint
|
||||||
|
- Workaround: Frontend includes user_id in query param, but no validation that it matches auth token
|
||||||
|
|
||||||
|
**Missing Auth Validation on Read Endpoints:**
|
||||||
|
- Symptoms: `/api/logs`, `/api/logs/last/...`, `/api/progression/...` do not require auth; anyone can see anyone's data
|
||||||
|
- Files: `backend/src/index.js` (lines 252-279, 282-296, 332-383 — none have `authMiddleware`)
|
||||||
|
- Trigger: Unauthenticated request to any of these endpoints returns full data
|
||||||
|
- Workaround: None; endpoint is truly public
|
||||||
|
|
||||||
|
**Profile Fetch Endpoints Bypass Auth:**
|
||||||
|
- Symptoms: `/api/user/profile` GET and other profile endpoints sometimes called without token
|
||||||
|
- Files: `frontend/src/pages/ProfilePage.jsx` (lines 25-27 make calls with optional header)
|
||||||
|
- Trigger: Token expires or not in localStorage; frontend still tries to fetch profile
|
||||||
|
- Workaround: Redirect on 401, but data may be partially loaded
|
||||||
|
|
||||||
|
**Onboarding Does Not Validate Strength Input:**
|
||||||
|
- Symptoms: Can enter non-numeric strength values; API accepts and stores as invalid data
|
||||||
|
- Files: `frontend/src/pages/OnboardingWizard.jsx` (lines 150-153); `backend/src/index.js` (no validation)
|
||||||
|
- Trigger: Enter "abc" in 1RM field; submit saves to database
|
||||||
|
- Workaround: None; data is corrupted
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
**Authentication Not Required on Data Endpoints:**
|
||||||
|
- Risk: Public endpoints `/api/logs`, `/api/progression/...`, `/api/logs/last/...` expose all user workout data without auth
|
||||||
|
- Files: `backend/src/index.js` (routes at lines 252, 282, 332)
|
||||||
|
- Current mitigation: None; these routes are public
|
||||||
|
- Recommendations: Add `authMiddleware` to all endpoints that return user data; validate user_id from request matches decoded token
|
||||||
|
|
||||||
|
**User ID Not Validated on Update Operations:**
|
||||||
|
- Risk: Client sends PUT request with any user_id; no validation that it matches auth token
|
||||||
|
- Files: `backend/src/index.js` (lines 103, 121, 153, 299)
|
||||||
|
- Current mitigation: Database constraint on user_id, but not enforced at API level
|
||||||
|
- Recommendations: Extract user_id from JWT token (`req.user.id`); never accept from request body; validate ownership
|
||||||
|
|
||||||
|
**Password Hashing Strength Acceptable But Not Tested:**
|
||||||
|
- Risk: Uses bcryptjs with rounds=10 (line 39); no test for hash strength
|
||||||
|
- Files: `backend/src/index.js` (line 39)
|
||||||
|
- Current mitigation: bcryptjs with 10 rounds is secure
|
||||||
|
- Recommendations: Increase to 12+ rounds; add integration test for password hashing
|
||||||
|
|
||||||
|
**JWT Token Expiry Too Long:**
|
||||||
|
- Risk: 30-day token expiry is long; stolen token has extended window
|
||||||
|
- Files: `backend/src/index.js` (lines 44, 61)
|
||||||
|
- Current mitigation: Token stored in localStorage; vulnerable to XSS
|
||||||
|
- Recommendations: Reduce to 1-7 days; implement refresh token rotation; consider httpOnly cookies
|
||||||
|
|
||||||
|
**No CORS Validation:**
|
||||||
|
- Risk: CORS enabled for all origins (`app.use(cors())`)
|
||||||
|
- Files: `backend/src/index.js` (line 19)
|
||||||
|
- Current mitigation: None; frontend is localhost during dev, but prod deployment may not restrict
|
||||||
|
- Recommendations: Add whitelist: `cors({ origin: process.env.FRONTEND_URL })`
|
||||||
|
|
||||||
|
**Database Connection Not SSL in Docker:**
|
||||||
|
- Risk: PostgreSQL connection from Docker unencrypted if over network
|
||||||
|
- Files: `backend/src/index.js` (lines 11-17); `docker-compose.yml`
|
||||||
|
- Current mitigation: On internal `homelab` network, but not encrypted
|
||||||
|
- Recommendations: Add `ssl: true` to pool config if connecting over untrusted network
|
||||||
|
|
||||||
|
## Performance Bottlenecks
|
||||||
|
|
||||||
|
**N+1 Query Problem in Program Endpoints:**
|
||||||
|
- Problem: `/api/programs/:id` loads program, then for each day, joins exercises separately
|
||||||
|
- Files: `backend/src/index.js` (lines 196-231)
|
||||||
|
- Cause: Single query with complex LEFT JOINs and json_agg; works but could be optimized with batching
|
||||||
|
- Improvement path: Already optimized with single query; no issue here. Performance is acceptable
|
||||||
|
|
||||||
|
**No Database Indexes on Common Queries:**
|
||||||
|
- Problem: `workout_logs` queries filter by `(user_id, date, program_exercise_id)` but indexes only on two columns
|
||||||
|
- Files: `db/init.sql` (line 77); `backend/src/index.js` (lines 252-279)
|
||||||
|
- Cause: Missing composite index on `(user_id, date)` and separate on `program_exercise_id`
|
||||||
|
- Improvement path: Add `CREATE INDEX idx_workout_logs_user_date_exercise ON workout_logs(user_id, date, program_exercise_id)`
|
||||||
|
|
||||||
|
**Measurements Fetch Not Limited:**
|
||||||
|
- Problem: `/api/user/measurements` returns up to 100 rows without pagination
|
||||||
|
- Files: `backend/src/index.js` (line 142)
|
||||||
|
- Cause: LIMIT 100 hardcoded; if user has years of data, transfers unnecessary payload
|
||||||
|
- Improvement path: Add `limit` and `offset` query params; default to last 30 records
|
||||||
|
|
||||||
|
**Strength History Not Limited:**
|
||||||
|
- Problem: `/api/user/strength` also LIMIT 100
|
||||||
|
- Files: `backend/src/index.js` (line 174)
|
||||||
|
- Cause: Same as measurements
|
||||||
|
- Improvement path: Add pagination; default to last 12 records (1 year monthly checks)
|
||||||
|
|
||||||
|
**Frontend Fetches All Logs for All Exercises at Once:**
|
||||||
|
- Problem: `App.jsx` `fetchLogs()` makes one request per exercise (loop)
|
||||||
|
- Files: `frontend/src/App.jsx` (lines 35-51)
|
||||||
|
- Cause: Not batched; if 6 exercises, makes 6 API calls sequentially
|
||||||
|
- Improvement path: Batch into single endpoint; return all day's logs in one query
|
||||||
|
|
||||||
|
**Progression Calculation Fetches Last 10 Logs Per Request:**
|
||||||
|
- Problem: `/api/progression/:id` fetches last 10 logs for every exercise opened
|
||||||
|
- Files: `backend/src/index.js` (line 354)
|
||||||
|
- Cause: Called on component expand; if user expands 6 exercises, 6 queries
|
||||||
|
- Improvement path: Batch progression calculations; include in WorkoutPage's initial fetch
|
||||||
|
|
||||||
|
## Fragile Areas
|
||||||
|
|
||||||
|
**AuthContext Token Refresh Not Automatic:**
|
||||||
|
- Files: `frontend/src/context/AuthContext.jsx`
|
||||||
|
- Why fragile: 30-day token expiry means users logged in for <30d get sudden 401. No refresh token mechanism. Token stored in localStorage (XSS vulnerable)
|
||||||
|
- Safe modification: Add refresh token endpoint; implement automatic refresh-before-expiry; consider httpOnly cookies
|
||||||
|
- Test coverage: No tests; AuthContext has no test file
|
||||||
|
|
||||||
|
**Profile Fetch Without Error Boundaries:**
|
||||||
|
- Files: `frontend/src/pages/ProfilePage.jsx` (lines 22-42)
|
||||||
|
- Why fragile: Fetch errors caught in console but no UI feedback; Promise.all() fails if one fetch fails, but all three fetches (profile, measurements, strength) are separate queries
|
||||||
|
- Safe modification: Wrap each fetch in try-catch separately; show partial data if some fail; add error toast
|
||||||
|
- Test coverage: No tests
|
||||||
|
|
||||||
|
**Onboarding State Not Persisted During Request:**
|
||||||
|
- Files: `frontend/src/pages/OnboardingWizard.jsx` (lines 24-72)
|
||||||
|
- Why fragile: If user fills all 4 steps and network fails during save, form clears but data lost. No draft save
|
||||||
|
- Safe modification: Auto-save to localStorage after each step; restore on remount
|
||||||
|
- Test coverage: No tests; body fat calculation not tested
|
||||||
|
|
||||||
|
**Warmup Completion State Lost on Navigation:**
|
||||||
|
- Files: `frontend/src/pages/WorkoutPage.jsx` (lines 51-53, 79-87)
|
||||||
|
- Why fragile: `completedWarmups` is local state in component; navigating away loses progress. No persistence
|
||||||
|
- Safe modification: Save to localStorage keyed by date+day
|
||||||
|
- Test coverage: No tests
|
||||||
|
|
||||||
|
**Exercise Progression Display Race Condition:**
|
||||||
|
- Files: `frontend/src/pages/WorkoutPage.jsx` (lines 48-67)
|
||||||
|
- Why fragile: `loadProgressions()` called in useEffect with `[day]` dependency; if day changes rapidly, multiple requests in flight; setState after unmount possible
|
||||||
|
- Safe modification: Add cleanup function; abort controller for fetch; cache by day ID
|
||||||
|
- Test coverage: No tests
|
||||||
|
|
||||||
|
**Database Schema Missing User FK in Measurements:**
|
||||||
|
- Files: `db/init.sql` (lines 64-74)
|
||||||
|
- Why fragile: `user_measurements` table has no explicit FOREIGN KEY to users table; can create orphaned records; cascading delete not enforced
|
||||||
|
- Safe modification: Add `user_id INTEGER REFERENCES users(id) ON DELETE CASCADE NOT NULL`
|
||||||
|
- Test coverage: Schema not tested
|
||||||
|
|
||||||
|
**Hardcoded Warmup Exercises Not Database-Driven:**
|
||||||
|
- Files: `frontend/src/pages/WorkoutPage.jsx` (lines 5-35)
|
||||||
|
- Why fragile: Warmup data hardcoded in component; if new muscle group added to exercises, no warmups for it; mapping is manual and fragile
|
||||||
|
- Safe modification: Move to database table `warmup_exercises(muscle_group, name, duration, type)`; fetch on page load
|
||||||
|
- Test coverage: No tests; mapping logic untested
|
||||||
|
|
||||||
|
## Scaling Limits
|
||||||
|
|
||||||
|
**Single Program Per User:**
|
||||||
|
- Current capacity: System assumes all users follow program_id=1
|
||||||
|
- Limit: Cannot support multiple programs or user switching between programs
|
||||||
|
- Scaling path: Refactor endpoints to accept program_id; add `user_programs` join table; allow user to select active program
|
||||||
|
|
||||||
|
**No Pagination on History Endpoints:**
|
||||||
|
- Current capacity: `/api/user/measurements` returns LIMIT 100; `/api/user/strength` returns LIMIT 100
|
||||||
|
- Limit: If user has >100 measurements (100+ days of data), response grows indefinitely
|
||||||
|
- Scaling path: Implement cursor-based pagination; return 20-30 records per page; add date range filters
|
||||||
|
|
||||||
|
**Database Connection Pool Not Configured:**
|
||||||
|
- Current capacity: `pg` module defaults to pool size 10
|
||||||
|
- Limit: 10 concurrent connections; 11th request queues
|
||||||
|
- Scaling path: Add explicit pool configuration: `max: 20, min: 5` adjusted per load; monitor with `pg_stat_activity`
|
||||||
|
|
||||||
|
**Logs Stored Flat Without Aggregate Summary:**
|
||||||
|
- Current capacity: Every set logged individually; querying 100 workouts × 6 exercises × 3 sets = 1800 rows
|
||||||
|
- Limit: As user history grows, workout fetches slow down
|
||||||
|
- Scaling path: Add `workout_sessions` table with aggregate stats; denormalize common queries
|
||||||
|
|
||||||
|
**Frontend Loads Entire Program Structure:**
|
||||||
|
- Current capacity: `/api/programs/:id` returns all days + all exercises in one response
|
||||||
|
- Limit: With 12+ exercises per day, response grows; not a problem now but scales poorly
|
||||||
|
- Scaling path: Lazy-load exercises per day; separate endpoint for day details; cache aggressively
|
||||||
|
|
||||||
|
## Dependencies at Risk
|
||||||
|
|
||||||
|
**Express 4.x Minor Versions:**
|
||||||
|
- Risk: No automatic security updates; express vulnerabilities not patched unless manually updated
|
||||||
|
- Impact: Known CVEs in middleware could be exploited
|
||||||
|
- Migration plan: Upgrade to Express 5.x (breaking changes); or add `npm audit fix` to CI pipeline
|
||||||
|
|
||||||
|
**bcryptjs No Longer Maintained:**
|
||||||
|
- Risk: bcryptjs is unmaintained library; use native Node.js crypto instead
|
||||||
|
- Impact: Security bugs in bcryptjs won't be fixed; but practical risk is low (bcrypt algorithm is solid)
|
||||||
|
- Migration plan: Switch to `bcrypt` (native binding) or Node.js `crypto.scrypt()` + built-in functions
|
||||||
|
|
||||||
|
**jsonwebtoken Known Vulns:**
|
||||||
|
- Risk: `jsonwebtoken` has history of algorithm confusion vulnerabilities (prior versions)
|
||||||
|
- Impact: Current version 9.0.2 (in package.json) is recent; likely patched
|
||||||
|
- Migration plan: Keep up with minor/patch updates; add `npm audit` to CI
|
||||||
|
|
||||||
|
**pg Library Version:**
|
||||||
|
- Risk: `pg` 8.11.3 is from 2023; no known critical issues but check advisories
|
||||||
|
- Impact: Low; PostgreSQL driver is stable
|
||||||
|
- Migration plan: Keep updated; monitor npm advisories
|
||||||
|
|
||||||
|
## Missing Critical Features
|
||||||
|
|
||||||
|
**No Input Sanitization:**
|
||||||
|
- Problem: User inputs not sanitized before database storage; e.g., XSS in exercise names, injection in auth fields
|
||||||
|
- Blocks: Any user-generated content features (notes, comments); social features
|
||||||
|
- Fix approach: Add input sanitization library (e.g., DOMPurify for frontend, `xss` for backend); validate at both layers
|
||||||
|
|
||||||
|
**No Rate Limiting:**
|
||||||
|
- Problem: No rate limits on auth endpoints; brute force attack possible on `/api/auth/login`
|
||||||
|
- Blocks: Public deployment; production security
|
||||||
|
- Fix approach: Add `express-rate-limit` middleware; 5 attempts per 15 min per IP
|
||||||
|
|
||||||
|
**No Audit Logging:**
|
||||||
|
- Problem: No record of who did what when; can't detect unauthorized access or data changes
|
||||||
|
- Blocks: Compliance requirements; forensics
|
||||||
|
- Fix approach: Add `audit_logs` table; log all create/update/delete with user_id, timestamp, action
|
||||||
|
|
||||||
|
**No Soft Deletes:**
|
||||||
|
- Problem: No way to recover deleted data; hard deletes cascade immediately
|
||||||
|
- Blocks: Undo features; data recovery
|
||||||
|
- Fix approach: Add `deleted_at` column to tables; use soft deletes; implement undelete mechanism
|
||||||
|
|
||||||
|
**No API Versioning:**
|
||||||
|
- Problem: No v1, v2 paths; breaking changes would affect all clients
|
||||||
|
- Blocks: Safe API evolution
|
||||||
|
- Fix approach: Add `/api/v1/...` prefix; maintain backward compatibility when possible
|
||||||
|
|
||||||
|
## Test Coverage Gaps
|
||||||
|
|
||||||
|
**No Backend Tests:**
|
||||||
|
- What's not tested: All 425 lines of `backend/src/index.js` have zero test coverage
|
||||||
|
- Files: `backend/src/index.js`
|
||||||
|
- Risk: Auth logic not verified; SQL injection prevention untested; progression calculation not validated; error paths not covered
|
||||||
|
- Priority: High
|
||||||
|
|
||||||
|
**No Frontend Unit Tests:**
|
||||||
|
- What's not tested: No Jest/Vitest config; components not tested
|
||||||
|
- Files: `frontend/src/**/*.jsx` (all files)
|
||||||
|
- Risk: UI bugs not caught; hooks logic untested; state transitions not verified
|
||||||
|
- Priority: High
|
||||||
|
|
||||||
|
**No Integration Tests:**
|
||||||
|
- What's not tested: API-to-database flow untested; full workout logging flow untested
|
||||||
|
- Risk: Database schema changes break endpoints; race conditions in concurrent requests not caught
|
||||||
|
- Priority: Medium
|
||||||
|
|
||||||
|
**No E2E Tests:**
|
||||||
|
- What's not tested: User flows untested (register → onboard → log workout → progress)
|
||||||
|
- Risk: Broken onboarding, broken login, navigation issues in production not caught
|
||||||
|
- Priority: Medium
|
||||||
|
|
||||||
|
**Auth Logic Not Tested:**
|
||||||
|
- What's not tested: Token verification, expiry, malformed tokens, missing auth headers
|
||||||
|
- Files: `backend/src/index.js` (lines 22-29); `frontend/src/context/AuthContext.jsx`
|
||||||
|
- Risk: Auth bypass vulnerabilities not detected
|
||||||
|
- Priority: Critical
|
||||||
|
|
||||||
|
**Progression Calculation Not Tested:**
|
||||||
|
- What's not tested: Algorithm for suggesting weight increases
|
||||||
|
- Files: `backend/src/index.js` (lines 332-383)
|
||||||
|
- Risk: Incorrect progression logic goes unnoticed; users stay on same weight or jump too much
|
||||||
|
- Priority: High
|
||||||
|
|
||||||
|
**Database Schema Not Validated:**
|
||||||
|
- What's not tested: Foreign key constraints, cascading deletes, data types
|
||||||
|
- Files: `db/init.sql`
|
||||||
|
- Risk: Invalid data created; orphaned records; type mismatches
|
||||||
|
- Priority: Medium
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Concerns audit: 2026-02-15*
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
# Coding Conventions
|
||||||
|
|
||||||
|
**Analysis Date:** 2026-02-15
|
||||||
|
|
||||||
|
## Naming Patterns
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Frontend pages: PascalCase with `.jsx` extension (e.g., `Dashboard.jsx`, `LoginPage.jsx`, `WorkoutPage.jsx`)
|
||||||
|
- Frontend components: PascalCase with `.jsx` extension (e.g., `Icons.jsx`)
|
||||||
|
- Backend routes: `index.js` for main server file
|
||||||
|
- CSS files: kebab-case or match component name (e.g., `index.css`, `App.css`)
|
||||||
|
- Context files: Named with `Context` suffix (e.g., `AuthContext.jsx`)
|
||||||
|
|
||||||
|
**Functions:**
|
||||||
|
- Async functions: verb + noun pattern (e.g., `fetchProgram`, `fetchLogs`, `handleSubmit`)
|
||||||
|
- Event handlers: `handle` prefix (e.g., `handleSubmit`, `handleSave`, `handleChange`)
|
||||||
|
- Helper/utility functions: descriptive names without prefixes (e.g., `getCoachGreeting`, `getMuscleGroups`, `getWeekStart`)
|
||||||
|
- Hook usage: Standard React hooks (e.g., `useState`, `useEffect`, `useContext`)
|
||||||
|
- Middleware functions: descriptive names (e.g., `authMiddleware`)
|
||||||
|
|
||||||
|
**Variables:**
|
||||||
|
- State variables: camelCase (e.g., `user`, `loading`, `program`, `selectedDay`)
|
||||||
|
- Constants (config): UPPER_SNAKE_CASE or camelCase (e.g., `API_URL`, `JWT_SECRET`, `PORT`)
|
||||||
|
- Local variables: camelCase (e.g., `dayOfWeek`, `todayWorkout`, `lastWeight`)
|
||||||
|
- Boolean variables: descriptive (e.g., `loading`, `editing`, `warmupDone`, `completedWarmups`)
|
||||||
|
- IDs: numeric or snake_case from database (e.g., `user_id`, `program_exercise_id`, `program_day_id`)
|
||||||
|
|
||||||
|
**Types:**
|
||||||
|
- Objects/interfaces: use descriptive structure without explicit types (e.g., `{ id, email, onboarding_complete }`)
|
||||||
|
- Database records: snake_case field names from schema (e.g., `password_hash`, `body_fat_pct`, `measured_at`)
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
**Formatting:**
|
||||||
|
- No explicit linter/formatter detected in config
|
||||||
|
- Indentation: 2 spaces (observed in code)
|
||||||
|
- Line length: typically under 100 characters
|
||||||
|
- Quotes: single quotes in most files, double quotes in some (inconsistent but not enforced)
|
||||||
|
- Semicolons: inconsistently used (some files omit, some include)
|
||||||
|
|
||||||
|
**Linting:**
|
||||||
|
- No ESLint, Prettier, or Biome config files detected
|
||||||
|
- No type checking (no TypeScript or JSDoc type annotations)
|
||||||
|
|
||||||
|
**Spacing:**
|
||||||
|
- Components separated by blank lines
|
||||||
|
- Function logic blocks separated by comments
|
||||||
|
- Import statements grouped: React/library imports first, then local imports
|
||||||
|
|
||||||
|
## Import Organization
|
||||||
|
|
||||||
|
**Order:**
|
||||||
|
1. React and core library imports (e.g., `import React from 'react'`)
|
||||||
|
2. External libraries (e.g., `react-router-dom`, Express packages)
|
||||||
|
3. Local imports (context, pages, components)
|
||||||
|
4. CSS/asset imports (e.g., `import './index.css'`)
|
||||||
|
|
||||||
|
**Path Aliases:**
|
||||||
|
- No path aliases configured (`@/` style paths not used)
|
||||||
|
- Relative imports used throughout (e.g., `'./context/AuthContext'`, `'../components/Icons'`)
|
||||||
|
- Relative paths: `../` for parent directory navigation in page imports
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```javascript
|
||||||
|
// Frontend (AuthContext.jsx)
|
||||||
|
import { createContext, useContext, useState, useEffect } from 'react';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
import { Icon } from '../components/Icons';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
// Backend (index.js)
|
||||||
|
const express = require('express');
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
const bcrypt = require('bcryptjs');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
**Patterns:**
|
||||||
|
- **Frontend (React):** Try-catch blocks in async functions, error state managed with `useState`
|
||||||
|
```javascript
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_URL}/auth/login`, { ... });
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.error);
|
||||||
|
// Handle success
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Backend (Express):** Try-catch blocks in route handlers with status code responses
|
||||||
|
```javascript
|
||||||
|
try {
|
||||||
|
// Database query or logic
|
||||||
|
res.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === '23505') return res.status(400).json({ error: 'Email already exists' });
|
||||||
|
console.error('Operation error:', err);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Error Response Format:** JSON with `error` key: `{ error: 'Human-readable message' }`
|
||||||
|
- **Status Codes:** 400 (validation/conflict), 401 (auth), 404 (not found), 500 (server error)
|
||||||
|
- **Empty error catches:** Some empty catch blocks without logging (e.g., `catch { logout(); }` in AuthContext)
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
**Framework:** Native `console.error()` only
|
||||||
|
|
||||||
|
**Patterns:**
|
||||||
|
- Error logging: `console.error('Context + error:', err)`
|
||||||
|
- Backend operations logged: `console.error('Register error:', err)`, `console.error('Profile error:', err)`
|
||||||
|
- Frontend operations: minimal logging (mostly silent failures)
|
||||||
|
- No structured logging or log levels (DEBUG, INFO, WARN)
|
||||||
|
- Log format: descriptive label + colon + error object
|
||||||
|
|
||||||
|
**Examples from code:**
|
||||||
|
```javascript
|
||||||
|
console.error('Failed to fetch program:', err);
|
||||||
|
console.error('Login error:', err);
|
||||||
|
console.error('Update profile error:', err);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
|
||||||
|
**When to Comment:**
|
||||||
|
- Section headers for major logical blocks (e.g., `// Coach section`, `// Today's action`, `// Quick stats`)
|
||||||
|
- Data structure explanations (e.g., `// Uppvärmningsövningar baserat på muskelgrupp`)
|
||||||
|
- Complex calculations or business logic
|
||||||
|
- Not applied to simple conditionals or obvious code
|
||||||
|
|
||||||
|
**JSDoc/TSDoc:**
|
||||||
|
- Not used - no type annotations or formal documentation
|
||||||
|
- Inline comments rare and minimal
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```javascript
|
||||||
|
// Mappa övningar till muskelgrupper
|
||||||
|
function getMuscleGroups(exercises) { ... }
|
||||||
|
|
||||||
|
// Beräkna progress
|
||||||
|
const completedExercises = exercises.filter(ex => { ... });
|
||||||
|
|
||||||
|
// Check if log exists for this set
|
||||||
|
const existing = await pool.query(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Function Design
|
||||||
|
|
||||||
|
**Size:**
|
||||||
|
- Page/component functions: 40-250 lines (includes JSX)
|
||||||
|
- Helper functions: 5-30 lines
|
||||||
|
- Backend route handlers: 10-50 lines
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- Named parameters for component props: `{ children, requireOnboarding = true }`
|
||||||
|
- Function parameters: individual arguments or destructured objects
|
||||||
|
- Query parameters: destructured from request (e.g., `const { user_id, date } = req.query`)
|
||||||
|
|
||||||
|
**Return Values:**
|
||||||
|
- React components return JSX directly
|
||||||
|
- Async functions return Promise<JSON | null>
|
||||||
|
- Helper functions return computed values or arrays
|
||||||
|
- Route handlers return via `res.json()` or `res.status().json()`
|
||||||
|
|
||||||
|
**Async/Await:**
|
||||||
|
- Preferred over `.then()` chains
|
||||||
|
- Used consistently in all async operations
|
||||||
|
- Combined with try-catch for error handling
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```javascript
|
||||||
|
const fetchProgram = async () => {
|
||||||
|
if (program) return; // Early return
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_URL}/programs/1`);
|
||||||
|
const data = await res.json();
|
||||||
|
setProgram(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch program:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function
|
||||||
|
function isSameDay(d1, d2) {
|
||||||
|
return d1.getDate() === d2.getDate() &&
|
||||||
|
d1.getMonth() === d2.getMonth() &&
|
||||||
|
d1.getFullYear() === d2.getFullYear();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Design
|
||||||
|
|
||||||
|
**Exports:**
|
||||||
|
- Frontend: Default exports for pages/contexts: `export default Dashboard`
|
||||||
|
- Frontend: Named exports for utilities: `export const useAuth = () => useContext(AuthContext)`
|
||||||
|
- Backend: Direct route handlers with `app.get()`, `app.post()` etc. (not module exports)
|
||||||
|
- Contexts: `export function AuthProvider` + `export const useAuth`
|
||||||
|
|
||||||
|
**Barrel Files:**
|
||||||
|
- Icons component (`Icons.jsx`) exports multiple icon definitions and helper functions
|
||||||
|
- Most modules single-responsibility (one component/context per file)
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```javascript
|
||||||
|
// Context export pattern
|
||||||
|
export function AuthProvider({ children }) { ... }
|
||||||
|
export const useAuth = () => useContext(AuthContext);
|
||||||
|
|
||||||
|
// Component export pattern
|
||||||
|
export default function LoginPage() { ... }
|
||||||
|
|
||||||
|
// Backend (no module export pattern, direct app routing)
|
||||||
|
app.post('/api/auth/login', async (req, res) => { ... });
|
||||||
|
```
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- React `useState` hooks for local component state
|
||||||
|
- React Context API for global auth state (`AuthContext.jsx`)
|
||||||
|
- Parent component state passed down as props (e.g., `App.jsx` manages view, program, logs)
|
||||||
|
- No Redux, Zustand, or Jotai
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- In-memory database connections via `Pool` (pg package)
|
||||||
|
- No state persistence between requests
|
||||||
|
- Request-scoped data via middleware (e.g., `req.user` from JWT)
|
||||||
|
|
||||||
|
## CSS/Styling
|
||||||
|
|
||||||
|
**Approach:** Plain CSS with CSS variables
|
||||||
|
- CSS variables defined in `:root`: `--bg-primary`, `--text-primary`, `--accent`, etc.
|
||||||
|
- Dark theme with fitness-oriented color palette
|
||||||
|
- Classes: descriptive kebab-case (e.g., `dashboard-header`, `calendar-day`, `page-main`)
|
||||||
|
- Utility/modifier classes: `.active`, `.today`, `.has-workout`, `.loading`
|
||||||
|
- No CSS-in-JS or utility framework (no Tailwind, Styled Components)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Convention analysis: 2026-02-15*
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# External Integrations
|
||||||
|
|
||||||
|
**Analysis Date:** 2026-02-15
|
||||||
|
|
||||||
|
## APIs & External Services
|
||||||
|
|
||||||
|
**None** - No external third-party APIs currently integrated.
|
||||||
|
|
||||||
|
## Data Storage
|
||||||
|
|
||||||
|
**Databases:**
|
||||||
|
- **PostgreSQL** (Primary)
|
||||||
|
- Connection via `pg` client library (8.11.3)
|
||||||
|
- Environment variables:
|
||||||
|
- `DB_HOST` - Default: `postgres` (Docker service name)
|
||||||
|
- `DB_PORT` - Default: `5432`
|
||||||
|
- `DB_USER` - Default: `postgres`
|
||||||
|
- `DB_PASSWORD` - Required secret
|
||||||
|
- `DB_NAME` - Default: `gravl`
|
||||||
|
- ORM/Client: `pg` (node-postgres) - Direct SQL queries, no ORM
|
||||||
|
- Initialization: `db/init.sql` - Schema and seed data
|
||||||
|
- Tables: users, programs, program_days, exercises, program_exercises, workout_logs, user_measurements, user_strength
|
||||||
|
|
||||||
|
**File Storage:**
|
||||||
|
- Local filesystem only - No external file storage service
|
||||||
|
- Static assets served by Nginx from built frontend `dist/` directory
|
||||||
|
|
||||||
|
**Caching:**
|
||||||
|
- None detected - No Redis, Memcached, or other caching layer
|
||||||
|
|
||||||
|
## Authentication & Identity
|
||||||
|
|
||||||
|
**Auth Provider:**
|
||||||
|
- Custom JWT-based authentication
|
||||||
|
- Implementation: `backend/src/index.js` (lines 22-29, 35-68)
|
||||||
|
- Token generation: `jsonwebtoken` 9.0.2
|
||||||
|
- Password hashing: `bcryptjs` 2.4.3
|
||||||
|
- Secret: `JWT_SECRET` environment variable (default: `gravl-secret-key-change-in-production`)
|
||||||
|
- Token expiration: 30 days
|
||||||
|
|
||||||
|
**Auth Flow:**
|
||||||
|
1. Frontend `AuthContext` (`frontend/src/context/AuthContext.jsx`) manages user state
|
||||||
|
2. User registers or logs in via `/api/auth/register` or `/api/auth/login`
|
||||||
|
3. Backend verifies credentials, generates JWT token
|
||||||
|
4. Frontend stores token in `localStorage` as `token`
|
||||||
|
5. Subsequent requests include `Authorization: Bearer {token}` header
|
||||||
|
6. Backend `authMiddleware` validates token on protected routes
|
||||||
|
7. User profile fetched on app load via `/api/user/profile`
|
||||||
|
|
||||||
|
**Protected Routes:**
|
||||||
|
- `/api/user/profile` - GET/PUT (requires auth)
|
||||||
|
- `/api/user/measurements` - GET/POST (requires auth)
|
||||||
|
- `/api/user/strength` - GET/POST (requires auth)
|
||||||
|
- `/api/logs` - GET/POST (no auth check in code - potential security gap)
|
||||||
|
- `/api/progression/:programExerciseId` - GET (no auth check - potential security gap)
|
||||||
|
|
||||||
|
**Public Routes:**
|
||||||
|
- `/api/health` - Health check endpoint
|
||||||
|
- `/api/auth/register` - User registration
|
||||||
|
- `/api/auth/login` - User login
|
||||||
|
- `/api/programs` - List all programs
|
||||||
|
- `/api/programs/:id` - Get program details
|
||||||
|
- `/api/days/:dayId/exercises` - Get exercises for a day
|
||||||
|
- `/api/today/:programId` - Get workout for day
|
||||||
|
|
||||||
|
## Frontend-Backend Communication
|
||||||
|
|
||||||
|
**API Base URL:**
|
||||||
|
- Hardcoded as `/api` in `frontend/src/context/AuthContext.jsx` (line 2)
|
||||||
|
- Development: Proxied by Vite to `http://localhost:3001`
|
||||||
|
- Production: Proxied by Nginx to `http://gravl-backend:3001`
|
||||||
|
|
||||||
|
**CORS:**
|
||||||
|
- Enabled on backend via `cors` middleware 2.8.5
|
||||||
|
- `app.use(cors())` in `backend/src/index.js` (line 19)
|
||||||
|
|
||||||
|
**HTTP Methods:**
|
||||||
|
- POST `/api/auth/register` - Register user
|
||||||
|
- POST `/api/auth/login` - Login user
|
||||||
|
- GET `/api/user/profile` - Get user profile
|
||||||
|
- PUT `/api/user/profile` - Update user profile
|
||||||
|
- POST `/api/user/measurements` - Add measurements
|
||||||
|
- GET `/api/user/measurements` - Get measurement history
|
||||||
|
- POST `/api/user/strength` - Add strength record
|
||||||
|
- GET `/api/user/strength` - Get strength history
|
||||||
|
- GET `/api/programs` - List programs
|
||||||
|
- GET `/api/programs/:id` - Get program with days and exercises
|
||||||
|
- GET `/api/days/:dayId/exercises` - Get exercises for day
|
||||||
|
- GET/POST `/api/logs` - Get/create workout logs
|
||||||
|
- GET `/api/logs/last/:programExerciseId` - Get last workout for exercise
|
||||||
|
- GET `/api/progression/:programExerciseId` - Calculate suggested weight
|
||||||
|
|
||||||
|
**Request/Response Format:**
|
||||||
|
- Content-Type: `application/json`
|
||||||
|
- Token: Passed in `Authorization: Bearer {token}` header
|
||||||
|
- Response: JSON objects
|
||||||
|
|
||||||
|
## Monitoring & Observability
|
||||||
|
|
||||||
|
**Error Tracking:**
|
||||||
|
- None detected - No Sentry, LogRocket, or similar
|
||||||
|
|
||||||
|
**Logs:**
|
||||||
|
- Backend: `console.error()` for errors (lines 48, 65, 98, 115, 133, 147, 165, 179, 190, 228, 246, 276, 294, 327, 380, 417)
|
||||||
|
- Frontend: No error tracking integrated
|
||||||
|
- Example: `console.error('Register error:', err)` in `backend/src/index.js` (line 48)
|
||||||
|
|
||||||
|
**Database Logging:**
|
||||||
|
- None detected - SQL queries not logged
|
||||||
|
|
||||||
|
## Webhooks & Callbacks
|
||||||
|
|
||||||
|
**Incoming:**
|
||||||
|
- None detected
|
||||||
|
|
||||||
|
**Outgoing:**
|
||||||
|
- None detected
|
||||||
|
|
||||||
|
## Environment Configuration
|
||||||
|
|
||||||
|
**Required env vars for backend:**
|
||||||
|
- `DB_HOST` - PostgreSQL hostname
|
||||||
|
- `DB_PORT` - PostgreSQL port
|
||||||
|
- `DB_USER` - Database user
|
||||||
|
- `DB_PASSWORD` - Database password (REQUIRED SECRET)
|
||||||
|
- `DB_NAME` - Database name
|
||||||
|
- `JWT_SECRET` - JWT signing secret (REQUIRED SECRET for production)
|
||||||
|
- `PORT` - Backend port (optional, default 3001)
|
||||||
|
|
||||||
|
**Secrets location:**
|
||||||
|
- Docker Compose: `docker-compose.yml` (lines 8-13) - **WARNING: Password visible in file**
|
||||||
|
- Backend defaults: `backend/src/index.js` (lines 9, 14-16)
|
||||||
|
- Frontend: None (token stored in browser localStorage)
|
||||||
|
|
||||||
|
**Traefik Integration:**
|
||||||
|
- Frontend exposed via Traefik reverse proxy
|
||||||
|
- Host: `gravl.homelab.local`
|
||||||
|
- HTTP and HTTPS support configured
|
||||||
|
- Networks: `proxy` (external), `homelab` (internal Docker Compose network)
|
||||||
|
|
||||||
|
## Service Dependencies
|
||||||
|
|
||||||
|
**Backend Dependencies:**
|
||||||
|
- PostgreSQL (required)
|
||||||
|
- Traefik proxy (for production routing)
|
||||||
|
|
||||||
|
**Frontend Dependencies:**
|
||||||
|
- Backend API at `/api` (required for all authenticated operations)
|
||||||
|
- Can operate in degraded mode if API is unavailable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Integration audit: 2026-02-15*
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
# Technology Stack
|
||||||
|
|
||||||
|
**Analysis Date:** 2026-02-15
|
||||||
|
|
||||||
|
## Languages
|
||||||
|
|
||||||
|
**Primary:**
|
||||||
|
- JavaScript (ES6+) - Both frontend and backend
|
||||||
|
- SQL - PostgreSQL database queries in backend
|
||||||
|
|
||||||
|
**Secondary:**
|
||||||
|
- HTML/CSS - Frontend UI styling
|
||||||
|
|
||||||
|
## Runtime
|
||||||
|
|
||||||
|
**Environment:**
|
||||||
|
- Node.js 20 (LTS) - Specified in Dockerfiles (`node:20-alpine`)
|
||||||
|
|
||||||
|
**Package Manager:**
|
||||||
|
- npm (Node Package Manager)
|
||||||
|
- Lockfile: `package-lock.json` present in both `frontend/` and `backend/`
|
||||||
|
|
||||||
|
## Frameworks
|
||||||
|
|
||||||
|
**Core:**
|
||||||
|
- **React** 18.2.0 - Frontend UI library (`frontend/package.json`)
|
||||||
|
- **Express.js** 4.18.2 - Backend REST API framework (`backend/package.json`)
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- **Vite** 5.0.8 - Frontend build tool and dev server
|
||||||
|
- Config: `frontend/vite.config.js`
|
||||||
|
- React plugin: `@vitejs/plugin-react` 4.2.1
|
||||||
|
|
||||||
|
**Routing:**
|
||||||
|
- **React Router DOM** 6.21.0 - Frontend client-side routing
|
||||||
|
- Configured in `frontend/src/main.jsx` with BrowserRouter and Routes
|
||||||
|
|
||||||
|
**Web Server:**
|
||||||
|
- **Nginx** (Alpine) - Production frontend server
|
||||||
|
- Config: `frontend/nginx.conf`
|
||||||
|
- Serves static assets, proxies `/api` to backend
|
||||||
|
- Gzip compression enabled
|
||||||
|
|
||||||
|
## Key Dependencies
|
||||||
|
|
||||||
|
**Frontend Critical:**
|
||||||
|
- `react` 18.2.0 - UI framework
|
||||||
|
- `react-dom` 18.2.0 - DOM rendering
|
||||||
|
- `react-router-dom` 6.21.0 - Client-side routing
|
||||||
|
|
||||||
|
**Frontend Dev:**
|
||||||
|
- `vite` 5.0.8 - Build tooling
|
||||||
|
- `@vitejs/plugin-react` 4.2.1 - React JSX support
|
||||||
|
- `@types/react` 18.2.43 - TypeScript types
|
||||||
|
- `@types/react-dom` 18.2.17 - TypeScript types
|
||||||
|
|
||||||
|
**Backend Critical:**
|
||||||
|
- `express` 4.18.2 - HTTP server framework
|
||||||
|
- `pg` 8.11.3 - PostgreSQL client library
|
||||||
|
- `jsonwebtoken` 9.0.2 - JWT authentication token generation/verification
|
||||||
|
- `bcryptjs` 2.4.3 - Password hashing and verification
|
||||||
|
- `cors` 2.8.5 - Cross-origin resource sharing middleware
|
||||||
|
|
||||||
|
**Backend Dev:**
|
||||||
|
- `nodemon` 3.0.2 - Auto-restart on file changes
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
**Environment:**
|
||||||
|
- Database connection via environment variables:
|
||||||
|
- `DB_HOST` - PostgreSQL hostname (default: `postgres`)
|
||||||
|
- `DB_PORT` - PostgreSQL port (default: `5432`)
|
||||||
|
- `DB_USER` - Database user (default: `postgres`)
|
||||||
|
- `DB_PASSWORD` - Database password
|
||||||
|
- `DB_NAME` - Database name (default: `gravl`)
|
||||||
|
- `JWT_SECRET` - JWT signing key (default: `gravl-secret-key-change-in-production`)
|
||||||
|
- `PORT` - Backend API port (default: `3001`)
|
||||||
|
|
||||||
|
**Build:**
|
||||||
|
- Frontend: `vite.config.js` - Vite configuration with React plugin
|
||||||
|
- Dev server: `0.0.0.0:5173`
|
||||||
|
- API proxy: `/api` routes to `http://localhost:3001`
|
||||||
|
- Backend: Simple Node.js entry point at `backend/src/index.js`
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
**Primary:**
|
||||||
|
- **PostgreSQL** - Relational database
|
||||||
|
- Initialized via `db/init.sql`
|
||||||
|
- Accessed via `pg` Node.js client library
|
||||||
|
- Tables: `users`, `programs`, `program_days`, `exercises`, `program_exercises`, `workout_logs`, `user_measurements`, `user_strength`
|
||||||
|
|
||||||
|
## Platform Requirements
|
||||||
|
|
||||||
|
**Development:**
|
||||||
|
- Node.js 20+
|
||||||
|
- npm 9+
|
||||||
|
- PostgreSQL 12+
|
||||||
|
- Docker and Docker Compose (for containerized development)
|
||||||
|
|
||||||
|
**Production:**
|
||||||
|
- Deployment target: Docker containers via Docker Compose
|
||||||
|
- Frontend container: `node:20-alpine` (build) → `nginx:alpine` (production)
|
||||||
|
- Backend container: `node:20-alpine`
|
||||||
|
- Reverse proxy: Traefik (configured in `docker-compose.yml`)
|
||||||
|
- Network: Homelab environment with internal proxy and homelab networks
|
||||||
|
|
||||||
|
## Build Process
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
1. `npm install` - Install dependencies
|
||||||
|
2. `npm run build` - Vite builds to `dist/` directory
|
||||||
|
3. Dockerfile multi-stage build:
|
||||||
|
- Stage 1: Node 20 Alpine - npm install and build
|
||||||
|
- Stage 2: Nginx Alpine - Serve built assets from `/usr/share/nginx/html`
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
1. `npm install --production` - Install dependencies (production only)
|
||||||
|
2. Dockerfile: Node 20 Alpine - Copy src and run `npm start`
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- `npm run dev` - Start Vite dev server on `0.0.0.0:5173` with hot reload
|
||||||
|
- `npm run build` - Production build to `dist/`
|
||||||
|
- `npm run preview` - Preview production build
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- `npm run start` - Run `node src/index.js` (production)
|
||||||
|
- `npm run dev` - Run with `nodemon` for auto-restart on file changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Stack analysis: 2026-02-15*
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
# Codebase Structure
|
||||||
|
|
||||||
|
**Analysis Date:** 2026-02-15
|
||||||
|
|
||||||
|
## Directory Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
gravl/
|
||||||
|
├── .git/ # Git repository
|
||||||
|
├── .planning/
|
||||||
|
│ └── codebase/ # Analysis documents (this file)
|
||||||
|
├── agents/ # AI agent configurations (not active codebase)
|
||||||
|
│ ├── architect/
|
||||||
|
│ ├── backend-dev/
|
||||||
|
│ ├── coach/
|
||||||
|
│ ├── frontend-dev/
|
||||||
|
│ ├── nutritionist/
|
||||||
|
│ └── reviewer/
|
||||||
|
├── backend/ # Express.js REST API server
|
||||||
|
│ ├── src/
|
||||||
|
│ │ └── index.js # Main server file (all routes and handlers)
|
||||||
|
│ ├── package.json # Backend dependencies
|
||||||
|
│ ├── Dockerfile # Docker build config
|
||||||
|
│ └── node_modules/ # Dependencies (not committed)
|
||||||
|
├── frontend/ # React SPA application
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── main.jsx # App entry point (routing, providers)
|
||||||
|
│ │ ├── App.jsx # Main app shell (view routing)
|
||||||
|
│ │ ├── index.css # Global styles
|
||||||
|
│ │ ├── App.css # App component styles
|
||||||
|
│ │ ├── pages/ # Full-page components (views)
|
||||||
|
│ │ ├── components/ # Reusable components
|
||||||
|
│ │ └── context/ # React Context providers
|
||||||
|
│ ├── index.html # HTML template
|
||||||
|
│ ├── vite.config.js # Vite build configuration
|
||||||
|
│ ├── package.json # Frontend dependencies
|
||||||
|
│ ├── Dockerfile # Docker build config
|
||||||
|
│ └── node_modules/ # Dependencies (not committed)
|
||||||
|
├── db/ # Database schema and initialization
|
||||||
|
│ └── init.sql # PostgreSQL schema definition
|
||||||
|
├── docker/ # Docker-related files
|
||||||
|
├── docker-compose.yml # Multi-container orchestration
|
||||||
|
├── README.md # Project overview
|
||||||
|
├── CLAUDE.md # LLM context file
|
||||||
|
└── TODO.md # Project tasks and notes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Purposes
|
||||||
|
|
||||||
|
**backend/src/:**
|
||||||
|
- Purpose: Backend application code
|
||||||
|
- Contains: Single Express server file with all routes, middleware, and database handlers
|
||||||
|
- Key files: `index.js` (14,361 lines, monolithic backend)
|
||||||
|
|
||||||
|
**frontend/src/:**
|
||||||
|
- Purpose: Frontend application source code
|
||||||
|
- Contains: React component files, styling, and global state management
|
||||||
|
- Key files: `App.jsx`, `main.jsx`, page components, AuthContext
|
||||||
|
|
||||||
|
**frontend/src/pages/:**
|
||||||
|
- Purpose: Full-page/route components (views)
|
||||||
|
- Contains: 8 page components handling entire view logic
|
||||||
|
- Key files:
|
||||||
|
- `Dashboard.jsx` - Main view showing program and scheduled workout
|
||||||
|
- `WorkoutPage.jsx` - Active workout tracking interface
|
||||||
|
- `ProfilePage.jsx` - User profile and measurements
|
||||||
|
- `ProgressPage.jsx` - Progress tracking and statistics
|
||||||
|
- `LoginPage.jsx` - Authentication entry
|
||||||
|
- `RegisterPage.jsx` - Account creation
|
||||||
|
- `OnboardingWizard.jsx` - Initial profile setup
|
||||||
|
- `WorkoutSelectPage.jsx` - Program/day selection
|
||||||
|
|
||||||
|
**frontend/src/components/:**
|
||||||
|
- Purpose: Reusable UI components
|
||||||
|
- Contains: Shared UI building blocks
|
||||||
|
- Key files: `Icons.jsx` - Icon system and icon name mapping
|
||||||
|
|
||||||
|
**frontend/src/context/:**
|
||||||
|
- Purpose: React Context providers for global state
|
||||||
|
- Contains: Authentication state and session management
|
||||||
|
- Key files: `AuthContext.jsx` - User login, registration, profile updates, token management
|
||||||
|
|
||||||
|
**db/:**
|
||||||
|
- Purpose: Database schema and initialization
|
||||||
|
- Contains: SQL scripts for schema creation and seed data
|
||||||
|
- Key files: `init.sql` - Creates 8 tables, indexes, and inserts PPL program template
|
||||||
|
|
||||||
|
**docker/:**
|
||||||
|
- Purpose: Docker-related configuration (currently minimal)
|
||||||
|
- Contains: Likely Dockerfile templates or configuration
|
||||||
|
|
||||||
|
## Key File Locations
|
||||||
|
|
||||||
|
**Entry Points:**
|
||||||
|
- `frontend/index.html` - HTML template that loads React app
|
||||||
|
- `frontend/src/main.jsx` - React bootstrap, BrowserRouter setup, routing definitions
|
||||||
|
- `frontend/src/App.jsx` - Main app shell, view routing, workout state management
|
||||||
|
- `backend/src/index.js` - Express server initialization, all API routes
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- `frontend/vite.config.js` - Vite build config, dev proxy setup
|
||||||
|
- `frontend/package.json` - React, React Router, Vite dependencies
|
||||||
|
- `backend/package.json` - Express, PostgreSQL driver, JWT, bcrypt dependencies
|
||||||
|
- `docker-compose.yml` - Service definitions, networking, Traefik routing labels
|
||||||
|
|
||||||
|
**Core Logic:**
|
||||||
|
- `frontend/src/context/AuthContext.jsx` - Authentication and session management
|
||||||
|
- `backend/src/index.js` - All API endpoints, auth middleware, database queries
|
||||||
|
- `db/init.sql` - Database schema and initial data
|
||||||
|
|
||||||
|
**Styling:**
|
||||||
|
- `frontend/src/index.css` - Global styles, CSS variables, base components
|
||||||
|
- `frontend/src/App.css` - Application layout styles
|
||||||
|
- `frontend/src/pages/*.jsx` - Inline inline className attributes (CSS-in-JS via CSS class selectors)
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- **Pages:** PascalCase with "Page" suffix (e.g., `LoginPage.jsx`, `WorkoutPage.jsx`)
|
||||||
|
- **Components:** PascalCase (e.g., `Icons.jsx`)
|
||||||
|
- **Context:** PascalCase with "Context" suffix (e.g., `AuthContext.jsx`)
|
||||||
|
- **Backend routes:** Lowercase with slashes (e.g., `/api/auth/login`, `/api/user/profile`)
|
||||||
|
- **Database tables:** Lowercase with underscores (e.g., `workout_logs`, `program_exercises`)
|
||||||
|
|
||||||
|
**Directories:**
|
||||||
|
- **Page directory:** `pages/` (plural)
|
||||||
|
- **Component directory:** `components/` (plural)
|
||||||
|
- **Context directory:** `context/` (singular, convention)
|
||||||
|
- **Backend:** `src/` (single index.js file, no subdirectories)
|
||||||
|
|
||||||
|
**Functions:**
|
||||||
|
- **React components:** PascalCase (e.g., `function Dashboard()`)
|
||||||
|
- **Hooks/helpers:** camelCase (e.g., `fetchProgram()`, `getCoachGreeting()`, `getMuscleGroups()`)
|
||||||
|
- **Constants:** camelCase (e.g., `API_URL`, `weekdays`, `warmupExercises`)
|
||||||
|
- **Middleware:** camelCase (e.g., `authMiddleware`)
|
||||||
|
|
||||||
|
**Variables:**
|
||||||
|
- **State:** camelCase (e.g., `user`, `loading`, `selectedDay`)
|
||||||
|
- **Props:** camelCase (e.g., `onStartWorkout`, `onNavigate`)
|
||||||
|
- **API endpoints:** Lowercase kebab-case in URLs, snake_case in query parameters and JSON bodies
|
||||||
|
|
||||||
|
**Types/Database:**
|
||||||
|
- **Columns:** snake_case (e.g., `password_hash`, `onboarding_complete`, `program_exercise_id`)
|
||||||
|
- **Tables:** Lowercase plural (e.g., `users`, `programs`, `workout_logs`)
|
||||||
|
- **Foreign keys:** Follow pattern `{table_id}` (e.g., `user_id`, `program_id`)
|
||||||
|
|
||||||
|
## Where to Add New Code
|
||||||
|
|
||||||
|
**New Feature (e.g., new page/view):**
|
||||||
|
- Primary code: `frontend/src/pages/{FeatureName}Page.jsx`
|
||||||
|
- Styling: Inline CSS class names in JSX or extend `App.css`
|
||||||
|
- API calls: Direct fetch in component useEffect hooks, passing API_URL from page file
|
||||||
|
- Routing: Add Route to `frontend/src/main.jsx` with Route path and component
|
||||||
|
- If requires auth: Wrap in `<ProtectedRoute>` wrapper in main.jsx
|
||||||
|
- If requires context: Use `useAuth()` hook from AuthContext
|
||||||
|
|
||||||
|
**New API Endpoint (backend):**
|
||||||
|
- Location: Add route handler in `backend/src/index.js`
|
||||||
|
- Pattern: Use `app.get()`, `app.post()`, `app.put()` with path and handler function
|
||||||
|
- Database: Use `pool.query()` for PostgreSQL queries with parameterized queries ($1, $2, etc.)
|
||||||
|
- Auth: Add `authMiddleware` parameter if endpoint requires authentication
|
||||||
|
- Response: Return `res.json()` with data or error object
|
||||||
|
- Error handling: Wrap in try-catch, return appropriate status codes (400, 401, 404, 500)
|
||||||
|
|
||||||
|
**New Component:**
|
||||||
|
- Location: `frontend/src/components/{ComponentName}.jsx`
|
||||||
|
- Export: Default export or named export function component
|
||||||
|
- Props: Accept props for reusability, avoid direct API calls
|
||||||
|
- Integration: Import into pages or other components as needed
|
||||||
|
|
||||||
|
**New Database Table/Schema Change:**
|
||||||
|
- Location: `db/init.sql`
|
||||||
|
- Pattern: Add CREATE TABLE statement with proper data types and constraints
|
||||||
|
- Relations: Use FOREIGN KEY references and ON DELETE CASCADE
|
||||||
|
- Indexes: Add indexes for frequently queried columns (user_id, date, etc.)
|
||||||
|
- Seed data: Use INSERT statements with ON CONFLICT DO NOTHING
|
||||||
|
- Application: Changes apply on container restart (init.sql runs every startup)
|
||||||
|
|
||||||
|
**Utilities/Helpers:**
|
||||||
|
- Location: Keep in page file if only used there, or create in `frontend/src/utils/` if reused
|
||||||
|
- Pattern: Export as named functions (no separate utils directory currently exists)
|
||||||
|
- Examples: `getCoachGreeting()`, `getMuscleGroups()`, `getWeekStart()` are defined in pages
|
||||||
|
|
||||||
|
**Authentication/State:**
|
||||||
|
- Location: Extend `frontend/src/context/AuthContext.jsx` if global
|
||||||
|
- Location: Add to page component state with useState if local to page
|
||||||
|
- Pattern: Use `useAuth()` hook for auth context, create custom hooks if reusable state pattern emerges
|
||||||
|
|
||||||
|
## Special Directories
|
||||||
|
|
||||||
|
**node_modules/:**
|
||||||
|
- Purpose: Installed npm dependencies
|
||||||
|
- Generated: Yes (by npm install)
|
||||||
|
- Committed: No (.gitignore)
|
||||||
|
- Notes: Frontend and backend have separate node_modules directories
|
||||||
|
|
||||||
|
**.git/:**
|
||||||
|
- Purpose: Git version control repository
|
||||||
|
- Generated: Yes (git init)
|
||||||
|
- Committed: N/A (git internal)
|
||||||
|
|
||||||
|
**.planning/codebase/:**
|
||||||
|
- Purpose: Architecture and codebase analysis documents
|
||||||
|
- Generated: Yes (by mapping tools)
|
||||||
|
- Committed: Yes (for orchestrator reference)
|
||||||
|
- Contains: ARCHITECTURE.md, STRUCTURE.md, and other analysis documents
|
||||||
|
|
||||||
|
**agents/:**
|
||||||
|
- Purpose: Agent configuration (not part of active codebase)
|
||||||
|
- Generated: Yes (from setup)
|
||||||
|
- Committed: Yes
|
||||||
|
- Notes: These are orchestrator definitions, not part of the running application
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Structure analysis: 2026-02-15*
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
# Testing Patterns
|
||||||
|
|
||||||
|
**Analysis Date:** 2026-02-15
|
||||||
|
|
||||||
|
## Test Framework
|
||||||
|
|
||||||
|
**Runner:**
|
||||||
|
- Not detected - no test runner configured in package.json
|
||||||
|
- No Vitest, Jest, Mocha, or other test framework installed
|
||||||
|
- No test scripts in `package.json` for either frontend or backend
|
||||||
|
|
||||||
|
**Assertion Library:**
|
||||||
|
- Not installed - no testing dependencies found
|
||||||
|
|
||||||
|
**Run Commands:**
|
||||||
|
- No test commands available
|
||||||
|
- Frontend: `npm run dev`, `npm run build`, `npm run preview`
|
||||||
|
- Backend: `npm start`, `npm run dev` (nodemon)
|
||||||
|
|
||||||
|
## Test File Organization
|
||||||
|
|
||||||
|
**Location:**
|
||||||
|
- No test files found in project
|
||||||
|
- No `.test.js`, `.spec.js`, `.test.jsx`, or `.spec.jsx` files in source directories
|
||||||
|
- No `__tests__` directories present
|
||||||
|
|
||||||
|
**Naming:**
|
||||||
|
- Not applicable - no tests exist
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
- Not applicable - no tests exist
|
||||||
|
|
||||||
|
## Current Test Status
|
||||||
|
|
||||||
|
**Coverage:**
|
||||||
|
- Not tested - zero test files, no coverage tooling
|
||||||
|
- No test requirements or targets defined
|
||||||
|
- No test configuration files (vitest.config.*, jest.config.*, etc.)
|
||||||
|
|
||||||
|
**View Coverage:**
|
||||||
|
- Not applicable - no coverage tools present
|
||||||
|
|
||||||
|
## Testing Gaps
|
||||||
|
|
||||||
|
### High Priority
|
||||||
|
|
||||||
|
**Authentication Flow:**
|
||||||
|
- Location: `frontend/src/context/AuthContext.jsx`, `frontend/src/pages/LoginPage.jsx`, `frontend/src/pages/RegisterPage.jsx`, `backend/src/index.js` (routes)
|
||||||
|
- Missing: Token validation, login/register error handling, token expiration, protected route behavior
|
||||||
|
- Risk: Auth system could silently fail or allow unauthorized access
|
||||||
|
|
||||||
|
**Workout Logging:**
|
||||||
|
- Location: `frontend/src/App.jsx` (logSet function), `backend/src/index.js` (POST /api/logs)
|
||||||
|
- Missing: Set creation/update, duplicate handling, weight/reps validation, concurrent updates
|
||||||
|
- Risk: Incorrect workout data, lost entries, or duplicate logs
|
||||||
|
|
||||||
|
**API Error Handling:**
|
||||||
|
- Location: All fetch calls in `frontend/src/**`, all route handlers in `backend/src/index.js`
|
||||||
|
- Missing: Network failures, timeout handling, malformed responses, edge cases
|
||||||
|
- Risk: Silent failures, infinite loading states, unhandled exceptions
|
||||||
|
|
||||||
|
### Medium Priority
|
||||||
|
|
||||||
|
**Profile Management:**
|
||||||
|
- Location: `frontend/src/pages/ProfilePage.jsx`, `frontend/src/pages/OnboardingWizard.jsx`, `backend/src/index.js` (user routes)
|
||||||
|
- Missing: Profile updates, measurements tracking, strength tracking, optional field handling
|
||||||
|
- Risk: Lost user data, incorrect profile state
|
||||||
|
|
||||||
|
**Program Navigation:**
|
||||||
|
- Location: `frontend/src/pages/Dashboard.jsx`, `frontend/src/App.jsx`, `backend/src/index.js` (program routes)
|
||||||
|
- Missing: Week/day navigation, today's workout calculation, day cycling logic
|
||||||
|
- Risk: Wrong workout shown, incorrect day assignments
|
||||||
|
|
||||||
|
**Data Validation:**
|
||||||
|
- Location: All form submissions (Login, Register, Profile updates), API inputs
|
||||||
|
- Missing: Email format validation, password requirements, numeric field bounds, null checks
|
||||||
|
- Risk: Invalid data persisted, server errors, SQL injection (though using parameterized queries)
|
||||||
|
|
||||||
|
### Low Priority
|
||||||
|
|
||||||
|
**UI State Management:**
|
||||||
|
- Location: `frontend/src/App.jsx`, `frontend/src/pages/Dashboard.jsx`
|
||||||
|
- Missing: View transitions, state consistency between pages, race conditions in state updates
|
||||||
|
- Risk: Inconsistent UI, stale data display
|
||||||
|
|
||||||
|
**Warmup Tracking:**
|
||||||
|
- Location: `frontend/src/pages/WorkoutPage.jsx`
|
||||||
|
- Missing: Warmup completion tracking, persistence, session state
|
||||||
|
- Risk: Lost warmup progress on page reload
|
||||||
|
|
||||||
|
## Recommended Testing Strategy
|
||||||
|
|
||||||
|
### Phase 1: Core Functionality
|
||||||
|
1. **Auth Integration Tests**
|
||||||
|
- Register → Login → Protected Route → Logout flow
|
||||||
|
- Error cases (invalid credentials, duplicate email)
|
||||||
|
- Token persistence across page reloads
|
||||||
|
|
||||||
|
2. **Workout Logging Integration Tests**
|
||||||
|
- Log set → Verify in state → Verify in API
|
||||||
|
- Update existing log vs create new
|
||||||
|
- Progression calculation
|
||||||
|
|
||||||
|
3. **API Unit Tests**
|
||||||
|
- Backend route handlers with mocked database
|
||||||
|
- Error handling (400, 401, 404, 500 status codes)
|
||||||
|
- Database constraint handling (duplicate email, foreign keys)
|
||||||
|
|
||||||
|
### Phase 2: Data Integrity
|
||||||
|
1. Form validation tests (Login, Register, Profile, Measurements)
|
||||||
|
2. Profile update consistency tests
|
||||||
|
3. Program/day/exercise relationship tests
|
||||||
|
|
||||||
|
### Phase 3: UI/UX
|
||||||
|
1. Component rendering tests (pages, conditional displays)
|
||||||
|
2. State transition tests (view changes, navigation)
|
||||||
|
3. Loading/error states display
|
||||||
|
|
||||||
|
## Testing Patterns (When Tests Are Added)
|
||||||
|
|
||||||
|
### Frontend (React) Pattern
|
||||||
|
```javascript
|
||||||
|
// Expected pattern for future tests
|
||||||
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import { AuthProvider } from '../context/AuthContext';
|
||||||
|
import LoginPage from '../pages/LoginPage';
|
||||||
|
|
||||||
|
describe('LoginPage', () => {
|
||||||
|
it('should submit login form with valid credentials', async () => {
|
||||||
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<AuthProvider>
|
||||||
|
<LoginPage />
|
||||||
|
</AuthProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('E-post'), { target: { value: 'test@example.com' } });
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('Lösenord'), { target: { value: 'password123' } });
|
||||||
|
fireEvent.click(screen.getByText('Logga in'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Loggar in...')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend (Express) Pattern
|
||||||
|
```javascript
|
||||||
|
// Expected pattern for future tests
|
||||||
|
const request = require('supertest');
|
||||||
|
const app = require('../index');
|
||||||
|
|
||||||
|
describe('POST /api/auth/login', () => {
|
||||||
|
it('should return 401 for invalid credentials', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/api/auth/login')
|
||||||
|
.send({ email: 'test@example.com', password: 'wrong' });
|
||||||
|
|
||||||
|
expect(res.status).toBe(401);
|
||||||
|
expect(res.body).toHaveProperty('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return token for valid credentials', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/api/auth/login')
|
||||||
|
.send({ email: 'test@example.com', password: 'correct' });
|
||||||
|
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.body).toHaveProperty('token');
|
||||||
|
expect(res.body).toHaveProperty('user');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup Recommendations
|
||||||
|
|
||||||
|
**Install testing dependencies:**
|
||||||
|
```bash
|
||||||
|
# Frontend
|
||||||
|
npm install --save-dev @testing-library/react @testing-library/jest-dom vitest
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
npm install --save-dev supertest jest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create config files:**
|
||||||
|
- `frontend/vitest.config.js` - Configure for React components
|
||||||
|
- `backend/jest.config.js` - Configure for Node.js
|
||||||
|
|
||||||
|
**Test structure:**
|
||||||
|
```
|
||||||
|
frontend/src/
|
||||||
|
__tests__/
|
||||||
|
context/
|
||||||
|
AuthContext.test.jsx
|
||||||
|
pages/
|
||||||
|
LoginPage.test.jsx
|
||||||
|
Dashboard.test.jsx
|
||||||
|
components/
|
||||||
|
Icons.test.jsx
|
||||||
|
|
||||||
|
backend/src/
|
||||||
|
__tests__/
|
||||||
|
auth.test.js
|
||||||
|
programs.test.js
|
||||||
|
logs.test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Testing analysis: 2026-02-15*
|
||||||
Reference in New Issue
Block a user