# 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*
