From a6fd59fab4668a55e7d90f51de382fc43a22c6d3 Mon Sep 17 00:00:00 2001 From: Daniel Onyejesi Date: Tue, 24 Mar 2026 02:57:04 -0400 Subject: [PATCH] Update DEVELOPER_GUIDE: PDF/embedding rationale, scalability, security notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Section 17: PDF uploads — why no vector embeddings needed (single-file generation uses full-text extraction which is better for this use case; explains when embeddings would be appropriate and how to increase truncation limit) - Section 18: Scalability — what already scales, bottlenecks (rate limiting, file uploads), horizontal scaling pattern with Redis + nginx, cloud options - Section 19: Security — localStorage vs httpOnly cookie decision documented with rationale; existing XSS protections listed; path to httpOnly if needed - Section 15: Version history updated and moved below new sections - Version bumped to 3.19, current Docker tag updated --- DEVELOPER_GUIDE.md | 114 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index b016300..56d7517 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -758,5 +758,119 @@ docker pull danielonyejesi/pediatric-ai-scribe-v3:latest --- +## 17. PDF Uploads — Why No Vector Embeddings + +The current approach for PDF/file uploads in the Learning Hub AI generator: + +1. User uploads a file (or picks from Nextcloud) +2. `pdf-parse` v1.1.1 extracts the full text from the PDF +3. The full text (up to 12,000 characters) is sent as context in the AI prompt +4. AI generates structured content (article body + quiz questions) based on that context + +**You do NOT need embeddings or RAG (Retrieval Augmented Generation) for this use case.** Here's why: + +| Scenario | Use embeddings? | Why | +|----------|----------------|-----| +| Upload 1 file → generate 1 article | ❌ No | Full text fits in context; AI sees everything | +| Search across 100+ stored documents | ✅ Yes | Too much text for one context window | +| Long PDF (200+ pages) | ✅ Maybe | Truncation at 12k chars; embeddings enable chunked retrieval | +| This app's current use case | ❌ No | Single file, single generation, full context is better | + +Embeddings (e.g. OpenAI `text-embedding-ada-002`, pgvector in PostgreSQL) would add complexity, cost, and storage requirements with no user-facing benefit for single-document content generation. The current approach is intentionally simple and correct. + +If documents exceed 12,000 characters, increase the truncation limit in `learningAI.js`: +```javascript +docText.substring(0, 12000) // increase if needed (watch token costs) +``` + +--- + +## 18. Scalability + +### Current Architecture (Single Instance) +The app runs as a single Node.js process. This is fine for a team/department deployment (tens to hundreds of concurrent users). + +### What Scales Well Already +- **Stateless JWT auth** — no server-side session store; any instance can validate any token +- **PostgreSQL** — handles concurrent connections well; supports read replicas +- **Lazy-loaded component HTML** — reduces initial page size; tabs load on demand +- **AI calls** — fully async; expensive calls don't block other requests + +### Bottlenecks to Address Before Horizontal Scaling + +| Issue | Current | Fix for multi-instance | +|-------|---------|----------------------| +| Rate limiting | In-memory (per process) | Replace with Redis (`rate-limit-redis`) | +| File uploads | `multer` in RAM | Route uploads to S3/object storage | +| Scheduled cleanup | `setTimeout` in server.js | Use a dedicated cron job or DB-scheduled task | + +### How to Scale Horizontally +```yaml +# docker-compose with 3 app replicas + nginx load balancer +services: + app: + image: danielonyejesi/pediatric-ai-scribe-v3:latest + deploy: + replicas: 3 + environment: + DATABASE_URL: postgresql://... # shared external Postgres + REDIS_URL: redis://redis:6379 # add when rate-limit-redis is wired + nginx: + image: nginx:alpine + # upstream: round-robin across app replicas + redis: + image: redis:7-alpine + postgres: + image: postgres:16-alpine +``` + +Cloud deployment options (all work with the current Docker image): +- **AWS ECS/Fargate** — managed containers, easy auto-scaling +- **Railway / Render / Fly.io** — simple push-to-deploy with Docker +- **Kubernetes** — full control, overkill for most deployments + +--- + +## 19. Security Architecture — localStorage vs httpOnly Cookies + +The app stores JWT tokens in `localStorage`. This is a deliberate choice appropriate for this scale. The key security facts: + +**Current protections in place (more important than storage location):** +- `Content-Security-Policy: script-src 'self'` — blocks all external scripts and inline JS (v3.18) +- Input sanitization via `sanitizeHtml()` allowlist on all user-generated HTML +- All 26 `onclick=` inline event handlers removed (v3.18) — reduces XSS surface +- Rate limiting on auth endpoints +- Helmet.js security headers +- Parameterized SQL queries throughout + +**The reality about localStorage vs httpOnly cookies:** +> "Unless you're a bank or large enterprise, it doesn't really matter. Focus on preventing XSS, because that's what actually matters... fundamentally, the security benefit of using httpOnly cookies is very minimal. If your site suffers any kind of XSS, it makes it slightly more difficult for an attacker to use the auth token." — Security engineering community consensus + +httpOnly cookies prevent token *copying* but not token *use* — an XSS attacker can still make authenticated requests on the user's behalf regardless of where the token is stored. + +**If you later want httpOnly cookies:** The infrastructure is already in place (cookie-parser, CORS `credentials:true`). The change is: (1) set cookie on login, (2) remove token from `getAuthHeaders()`, (3) add `/api/auth/logout` to clear cookie. See notes in `auth.js`. This was implemented and reverted in v3.14 — it works but adds CSRF considerations. + +**Token lifetime:** Currently 7 days. For higher security, reduce to 1-2 hours and add refresh token rotation. + +--- + +## 15. Version History (Recent) + +| Tag | Key changes | +|-----|-------------| +| v3.19 | Login flash fixed; presentation quiz option; feed labels corrected | +| v3.18 | pdf-parse v1.1.1; WebDAV selection UX; topic context on upload/WebDAV; inline refine bar; CSP unsafe-inline removed; webdav-path auth fix | +| v3.17 | AI panel CSS cascade bug fixed; quiz card redesign | +| v3.16 | DEVELOPER_GUIDE.md created | +| v3.15 | Auth reverted to localStorage; slide preview padding | +| v3.14 | AI panel context-aware options; delete wording per type | +| v3.13 | Slide preview in-page modal (arrow/swipe/keyboard) | +| v3.12 | Delete inline confirm; lighter login; Presentation type (Marp + PPTX) | +| v3.11 | AI content generation for Learning Hub (topic/file/Nextcloud) | +| v3.10 | Custom 404 page | +| v3.8 | Tiptap 2 (self-hosted, inline link bar, no popup) | + +--- + *Last updated: March 2026 — v3.19* *Generated for developer handover.*