- Custom SQLite task manager replacing TickTick wrapper - 73 tasks migrated from TickTick across 15 projects - RRULE recurrence engine with lazy materialization - Dashboard tasks widget (desktop sidebar + mobile card) - Tasks page with project tabs, add/edit/complete/delete - Security: locked ports to localhost, removed old containers - Gitea Actions runner configured and all 3 CI jobs passing - Fixed mobile overflow on dashboard cards - iOS Capacitor app shell (Second Brain) - Frontend/backend guide docs for adding new services - TickTick Google Calendar sync re-authorized Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
230 lines
10 KiB
Plaintext
230 lines
10 KiB
Plaintext
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
|