diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md new file mode 100644 index 0000000..0177ccb --- /dev/null +++ b/.planning/codebase/ARCHITECTURE.md @@ -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* diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md new file mode 100644 index 0000000..631ccfb --- /dev/null +++ b/.planning/codebase/CONCERNS.md @@ -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* diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md new file mode 100644 index 0000000..7a54e11 --- /dev/null +++ b/.planning/codebase/CONVENTIONS.md @@ -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 +- 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* diff --git a/.planning/codebase/INTEGRATIONS.md b/.planning/codebase/INTEGRATIONS.md new file mode 100644 index 0000000..40920a4 --- /dev/null +++ b/.planning/codebase/INTEGRATIONS.md @@ -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* diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md new file mode 100644 index 0000000..e3720cf --- /dev/null +++ b/.planning/codebase/STACK.md @@ -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* diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md new file mode 100644 index 0000000..ebf9668 --- /dev/null +++ b/.planning/codebase/STRUCTURE.md @@ -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 `` 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* diff --git a/.planning/codebase/TESTING.md b/.planning/codebase/TESTING.md new file mode 100644 index 0000000..cf688e8 --- /dev/null +++ b/.planning/codebase/TESTING.md @@ -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( + + + + + + ); + + 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*