Platform Codex — Full Build & Remediation Log
================================================
Date: 2026-03-29

PLATFORM OVERVIEW
=================
A premium SaaS-style personal dashboard integrating self-hosted services:
  - Budget (Actual Budget)
  - Inventory (NocoDB)
  - Fitness (SparkyFitness)
  - Trips
  - Reader (Miniflux)
  - Media (Shelfmark/Spotizerr/Booklore)

Stack: SvelteKit (Svelte 5 runes) + Python gateway + Express.js services
Orchestration: Docker Compose, 6 containers
Reverse proxy: Pangolin at dash.quadjourney.com
Gitea repo: gate.quadjourney.com/yusiboyz/platform


WHAT WAS BUILT
==============

1. Design System
   - Unified CSS token system: spacing (--sp-*), radius (--radius-*),
     shadows (--shadow-*), colors, typography (--text-*)
   - Migrated 235 raw values across 31 files
   - Documented in frontend-v2/DESIGN_SYSTEM.md

2. Gateway Modular Refactor
   - Split 1878-line server.py into 15 modular files:
     server.py (thin router), config.py, database.py, auth.py, proxy.py,
     dashboard.py, plus integrations/ directory (booklore, kindle, image_proxy,
     qbittorrent, etc.)
   - ThreadingHTTPServer for concurrent requests
   - 30-second per-user dashboard cache

3. Fitness API Integration
   - Replaced all mock data with real SparkyFitness backend API calls
   - AI food input with multi-item splitting (/api/fitness/foods/split)
   - Canonical food storage with dedup (naive singularize + pre-creation check)
   - Entry editing (quantity) and deletion
   - Confirmation modal for resolved items with quantity +/-
   - Local timezone date fix (was showing UTC = next day after 7pm CDT)
   - Per-user goals editing in Settings
   - Shared food database across users, independent fitness goals

4. Media App (Built from Scratch)
   - Books tab: search via Shelfmark/Anna's Archive, download + auto-import
     to Booklore, per-book library dropdown
   - Music tab: Spotify search via Spotizerr, track/album/artist/playlist
     search types, Spotify embed player, download progress polling
   - Library tab: Booklore library browser (237 books), cover resolution via
     ISBN -> Open Library with localStorage cache, book detail modal,
     format badges (EPUB/PDF), library filter pills
   - Send to Kindle: SMTP2GO API integration, per-book Kindle label selector
     ("Madiha"/"Hafsa"), sends from bookdrop or booklore-books volumes
   - "After download, also send to" Kindle option on book search

5. Multi-User Support
   - Created Madiha's account
   - Per-user nav visibility (cosmetic only, documented as such)
   - hiddenByUser map in +layout.server.ts
   - Shared food database, independent fitness goals

6. Frontend Architecture
   - SvelteKit with Svelte 5 runes ($state, $props, $derived, $effect, $bindable)
   - SvelteKit hooks.server.ts for auth on Immich/Karakeep proxies
   - Settings page: real API data, fitness goals editing, disconnect confirmation
     dialog, theme toggle, sign out


SECURITY REMEDIATION (Gitea Issues #1-#10)
==========================================

All 10 issues completed. Re-audited and verified in code on 2026-03-29.

#2 Auth Boundary
  - /api/auth/register disabled (403)
  - Gateway admin seeded from ADMIN_USERNAME/ADMIN_PASSWORD env vars only
  - Trips USERNAME/PASSWORD have no default fallback
  - Fitness user seed requires env vars (no "changeme" default)
  - All passwords use bcrypt

#3 Trips Sharing Security
  - handle_share_api enforces password via X-Share-Password header + bcrypt
  - share_password stored as bcrypt hash
  - All plaintext password logging removed
  - Existing plaintext passwords invalidated by migration
  - Dead hash_password function removed

#4 Fitness Authorization
  - All user_id query params enforced to authenticated user's own ID
  - /api/users returns only current user
  - Wildcard CORS removed

#5 Gateway Trust Model
  - Inventory and budget require API keys (X-API-Key middleware)
  - Token validation uses protected endpoints per service type
  - /debug-nocodb removed from inventory
  - /test removed from inventory
  - NocoDB search filter sanitized (strips operator injection chars)
  - SERVICE_LEVEL_AUTH renamed to GATEWAY_KEY_SERVICES
  - Trust model documented in docs/trust-model.md
  - Per-user vs gateway-key services clearly distinguished
  - Known limitations documented (no per-user isolation on shared services)

#6 Repository Hygiene
  - No .env or .db files tracked in git
  - .gitignore covers: .env*, *.db*, services/**/.env, data/, test-results/
  - .env.example updated with all current env vars (no secrets)

#7 Transport Security
  - Gateway: _internal_ssl_ctx removed entirely (internal services use plain HTTP)
  - Gateway: ssl import removed from config.py
  - Gateway: proxy.py uses urlopen() without context parameter
  - Gateway: logout cookie includes HttpOnly, Secure, SameSite=Lax
  - Gateway: image proxy uses default TLS + domain allowlist + content-type validation
  - Trips: all 5 CERT_NONE sites removed (OpenAI, Gemini, Google Places, Geocode)
  - Inventory: permissive cors() removed AND dead cors import removed
  - Budget: permissive cors() removed AND dead cors import removed

#8 Dependency Security
  - Budget path-to-regexp vulnerability fixed
  - .gitea/workflows/security.yml committed with 3 jobs + workflow_dispatch trigger
  - Gitea Actions enabled ([actions] ENABLED = true in app.ini)
  - Runner (gitea/act_runner) added to Gitea docker-compose
  - Runner registered as platform-runner on gitea_gitea network
  - Config sets container.network = gitea_gitea so job containers can git clone
  - Runner token stored in /media/yusiboyz/Media/Scripts/gitea/.env
  - All 3 jobs verified passing:
    - dependency-audit: SUCCESS (npm audit on budget + frontend)
    - secret-scanning: SUCCESS (no tracked .env/.db, no hardcoded secrets)
    - dockerfile-lint: SUCCESS (all Dockerfiles have USER + HEALTHCHECK)
  - Runner setup documented in .gitea/README.md

#9 Performance Hardening
  - Inventory /issues: server-side NocoDB WHERE filter (no full scan)
  - Inventory /needs-review-count: server-side filter + pageInfo.totalRows
  - Budget /summary: 1-minute cache
  - Budget /transactions/recent: 30-second cache
  - Budget /uncategorized-count: 2-minute cache
  - Budget buildLookups: 2-minute cache
  - Gateway /api/dashboard: 30-second per-user cache
  - Actual Budget per-account API constraint documented

#10 Deployment Hardening
  - All 6 containers run as non-root (appuser/node)
  - Health checks on gateway, trips, fitness, inventory, budget, frontend
  - PYTHONUNBUFFERED=1 on all Python services
  - Trips Dockerfile only copies server.py (not whole context)
  - Frontend uses multi-stage build


RE-AUDIT FINDINGS (2026-03-29)
==============================
1 inaccuracy found in prior report: CORS dead imports (const cors = require('cors'))
remained in inventory/server.js and budget/server.js after app.use(cors()) was removed.
Fixed by removing the dead imports.

All other claims verified accurate in code:
  - Trips TLS: zero CERT_NONE or check_hostname = False
  - Settings disconnect: confirm() dialog present
  - /test cleanup: no references remain
  - Cosmetic nav: documented as cosmetic-only, no false authz claims


GITEA ACTIONS RUNNER SETUP (2026-03-29)
=======================================
Problem: Workflow existed in repo but no runner was configured to execute it.

What was done:
  1. Added [actions] ENABLED = true to Gitea app.ini
     File: /media/yusiboyz/Media/Scripts/gitea/gitea/gitea/conf/app.ini
  2. Restarted Gitea to pick up config change
  3. Generated runner token: docker exec -u git gitea gitea actions generate-runner-token
  4. Added runner service to Gitea docker-compose:
     File: /media/yusiboyz/Media/Scripts/gitea/docker-compose.yml
     Image: gitea/act_runner:latest
     Container: gitea-runner
  5. Saved token in /media/yusiboyz/Media/Scripts/gitea/.env as RUNNER_TOKEN
  6. First attempt: job containers created on auto-generated network, could not
     reach server:3000 for git clone (hung on git fetch)
  7. Fix: created /data/config.yaml inside runner with container.network = gitea_gitea
     and set CONFIG_FILE=/data/config.yaml env var
  8. Recreated runner container (docker compose up -d runner) to pick up env change
  9. Triggered workflow via API: POST /api/v1/repos/yusiboyz/platform/actions/workflows/security.yml/dispatches
  10. All 3 jobs ran to completion: dependency-audit, secret-scanning, dockerfile-lint = SUCCESS
  11. Added workflow_dispatch trigger to security.yml for manual runs
  12. Updated .gitea/README.md with setup documentation

Key detail: job containers must be on gitea_gitea network to resolve "server:3000"
for git operations. Without this, git fetch hangs indefinitely.


BUGS FIXED DURING BUILD
========================
- SERVICE_MAP import bug: captured empty dict at import time, fixed with module reference
- Gateway Dockerfile missing modules: only copied server.py, fixed to copy all .py + integrations/
- Non-root container permission denied: fixed with COPY --chown=appuser
- Fitness date timezone: toISOString() returns UTC, fixed with local date construction
- Dashboard fitness widget not updating: plain let vs $state() in Svelte 5
- Food library empty: /api/fitness/foods/recent returns entry-shaped data, fixed mapFood
- Book covers from search: double-wrapped image proxy URLs, fixed to proxy directly
- Booklore cover API returns HTML: switched to Open Library + Google Books fallback
- Booklore books API too slow (14s): moved to lazy client-side cover resolution
- Fitness entries orphaned after DB reset: reassigned to new user IDs
- Madiha accidentally disconnected fitness: added confirm() dialog
- Double Kindle sends: actually processed + delivered SMTP2GO events, added debounce
- Kindle email typo: lowercase L vs uppercase I in address


MANUAL OPS ACTIONS
==================
1. Store admin password securely (set via ADMIN_PASSWORD env var)
2. Clean up local untracked .env files with real credentials if needed
3. Monitor @sveltejs/kit for a non-breaking cookie fix in future releases


ARCHITECTURE REFERENCE
======================
- Trust model: docs/trust-model.md
- CI workflows: .gitea/workflows/security.yml
- Runner setup: .gitea/README.md
- Design system: frontend-v2/DESIGN_SYSTEM.md
- Env var reference: .env.example
- Gitea instance: localhost:3300 (gate.quadjourney.com)
- Gitea compose: /media/yusiboyz/Media/Scripts/gitea/docker-compose.yml
- Platform compose: /media/yusiboyz/Media/Scripts/platform/docker-compose.yml
