# NaviDocs StackCP Deployment Architecture **Final Deployment Configuration - Production Ready** --- ## System Architecture ### High-Level Overview ``` ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ END USERS ┃ ┃ Browsers / Mobile Clients ┃ ┃ https://digital-lab.ca/navidocs/ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┬━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ HTTPS/TLS │ DNS resolution │ ┏━━━━━━━━━━━━━━━━━━━━━━▼━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ StackCP SHARED HOSTING SERVER ┃ ┃ ssh.gb.stackcp.com ┃ ┃ digital-lab.ca ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ │ │ ┌──────▼────────┐ ┌──────▼────────┐ │ LAYER 1 │ │ LAYER 2 │ │ WEB SERVER │ │ APPLICATION │ │ (Nginx) │ │ (Node.js) │ └──────┬────────┘ └──────┬────────┘ │ │ │ Port 80/443 │ Port 8001 │ Static files │ Express API │ Reverse proxy │ │ │ ┌──────▼────────────────────────────────────▼────────┐ │ PUBLIC HTML │ │ │ ~/public_html/digital-lab.ca/navidocs/ │ │ │ ├─ index.html (Vue 3 frontend) │ │ │ ├─ assets/ (JS/CSS bundles) │ │ │ └─ [static] │ │ │ │ │ │ LOCAL NODE.JS │ │ │ /tmp/navidocs/ (EXECUTABLE) │ │ │ ├─ server/ (Express app) │ │ │ ├─ routes/ (API endpoints) │ │ │ ├─ services/ (OCR, PDF, DB) │ │ │ ├─ workers/ (BullMQ background jobs) │ │ │ └─ node_modules/ (npm packages) │ │ │ │ │ │ DATA STORAGE │ │ │ ~/navidocs-data/ (PERSISTENT) │ │ │ ├─ .env (config + secrets) │ │ │ ├─ db/ (SQLite database) │ │ │ │ └─ navidocs.db (~10-100 MB) │ │ │ ├─ uploads/ (user documents) │ │ │ │ ├─ [uuid].pdf │ │ │ │ ├─ [uuid].jpg │ │ │ │ └─ [etc] │ │ │ └─ logs/ (application logs) │ │ │ ├─ app.log (main server) │ │ │ └─ ocr-worker.log (OCR jobs) │ │ │ │ │ │ EXTERNAL SERVICES │ │ │ Meilisearch (http://localhost:7700) │ │ │ ├─ Full-text search index │ │ │ └─ Running on StackCP (/tmp/...) │ │ │ │ │ └─────────────────────────────────────────┘ │ │ ┌──────────────▼─┐ │ INTERNALS │ │ - Express.js │ │ - SQLite │ │ - BullMQ │ │ - Tesseract │ │ - pdfjs │ │ - Mammoth │ │ - xlsx │ └───────────────┘ ``` --- ## Data Flow: Document Upload ``` USER (Browser) │ │ POST /api/upload ├─ multipart/form-data └─ file (PDF, JPG, DOCX, XLSX, TXT) │ ▼ NGINX (Reverse Proxy) │ ├─ Proxy to localhost:8001 │ (long timeout for large files) │ │ ▼ EXPRESS ROUTE: /api/upload │ ├─ 1. AUTHENTICATE │ ├─ Verify JWT token │ └─ Get user ID + organization │ ├─ 2. VALIDATE FILE SAFETY │ ├─ Check file size (max 50MB) │ ├─ Check MIME type │ ├─ Verify magic bytes (%PDF) │ └─ Sanitize with qpdf (if available) │ ├─ 3. SAVE FILE │ ├─ Generate UUID │ └─ Save to ~/navidocs-data/uploads/[UUID].[ext] │ ├─ 4. CREATE DATABASE RECORD │ ├─ INSERT into documents table │ ├─ Set status = 'processing' │ └─ Record metadata (user, org, filename, size) │ ├─ 5. QUEUE FOR PROCESSING │ ├─ Add job to BullMQ/SQLite queue │ ├─ Job type: 'process-document' │ └─ Return job_id to user (for progress tracking) │ └─ RESPOND TO USER ├─ 200 OK ├─ { │ "docId": "uuid-xxx", │ "jobId": "job-123", │ "status": "processing", │ "fileName": "manual.pdf" │ } └─ Client polls GET /api/jobs/job-123 for progress updates BACKGROUND WORKER (OCR Processing) │ ├─ 1. DEQUEUE JOB │ └─ Get document UUID from queue │ ├─ 2. DETERMINE FORMAT │ ├─ PDF? → document-processor.js │ ├─ Image? → process-image.js │ ├─ DOCX? → process-word.js │ ├─ XLSX? → process-excel.js │ └─ Text? → process-text.js │ ├─ 3. EXTRACT TEXT (format-specific) │ │ IF PDF: │ ├─ 1. Try native text extraction (pdfjs-dist) │ ├─ 2. If page has <50 chars, OCR that page (Tesseract) │ ├─ 3. Combine results → full document text │ └─ ⏱️ Result: 5 seconds (100-page PDF) │ │ IF IMAGE: │ ├─ 1. Resize if needed (sharp) │ ├─ 2. OCR with Tesseract │ └─ ⏱️ Result: 3 seconds per page │ │ IF DOCX: │ ├─ 1. Extract with mammoth │ └─ ⏱️ Result: <1 second │ │ IF XLSX: │ ├─ 1. Parse sheets │ ├─ 2. Convert to CSV/text │ └─ ⏱️ Result: <1 second │ ├─ 4. GENERATE TABLE OF CONTENTS │ ├─ Analyze structure (headings, sections) │ └─ Create hierarchical TOC │ ├─ 5. INDEX IN MEILISEARCH │ ├─ POST /indexes/navidocs-pages/documents │ ├─ Payload: │ │ { │ │ "id": "doc-uuid-xxx", │ │ "title": "Engine Manual", │ │ "content": "[full extracted text]", │ │ "boatId": "boat-uuid", │ │ "organization": "org-uuid", │ │ "createdAt": 1699875600 │ │ } │ └─ Meilisearch indexes → full-text search ready │ ├─ 6. LOG ACTIVITY (Timeline) │ ├─ INSERT into activity_log │ ├─ event_type: 'document_processed' │ ├─ event_title: 'Engine Manual processed' │ └─ Created timestamp │ ├─ 7. UPDATE DATABASE │ ├─ UPDATE documents SET status = 'ready' │ ├─ Set: page_count, text_length, toc │ └─ UPDATE job SET status = 'completed' │ └─ USER SEES RESULT ├─ "Document ready" ├─ "Search now available" └─ "Added to timeline" ``` --- ## Data Flow: Search Query ``` USER (Browser) │ │ GET /search?q=bilge%20pump │ ▼ FRONTEND (Vue 3) │ ├─ 1. FETCH SEARCH TOKEN │ ├─ GET /api/search/token │ ├─ Backend generates scoped Meilisearch token │ └─ Token is time-limited (1 hour) │ └─ 2. INITIALIZE MEILISEARCH CLIENT ├─ New MeiliSearch({ host, apiKey: token }) └─ Token restricts searches to user's documents CLIENT → MEILISEARCH (Direct) │ └─ POST /indexes/navidocs-pages/search ├─ Query: "bilge pump" ├─ Limit: 20 ├─ Filter: "organization = 'user-org'" │ (enforced by token) │ ▼ MEILISEARCH SEARCH ENGINE │ ├─ 1. TOKENIZE QUERY │ └─ "bilge" + "pump" │ ├─ 2. SEARCH INDEX │ ├─ Find pages containing both terms │ ├─ Rank by relevance (BM25 algorithm) │ └─ Filter by organization (tenant isolation) │ ├─ 3. HIGHLIGHT MATCHES │ └─ Bold matching words in results │ └─ RESPONSE (< 10ms) { "hits": [ { "id": "doc-1-page-5", "title": "Engine Manual", "content": "...The bilge pump is located...", "document_id": "doc-uuid", "page_number": 5, "boat_id": "boat-uuid" }, ... more results ], "totalHits": 42, "limit": 20 } │ ▼ FRONTEND DISPLAYS RESULTS │ ├─ List of matching pages ├─ Snippet with highlighted text ├─ Document title ├─ Page number (if available) └─ Click to view document ``` --- ## Data Flow: Timeline Activity ``` USER (Browser) │ │ GET /timeline │ ▼ FRONTEND (Vue 3) │ └─ GET /api/organizations/:orgId/timeline ├─ Start date (optional) ├─ End date (optional) └─ Limit (default: 50 events) │ ▼ EXPRESS ROUTE: /api/timeline │ ├─ 1. AUTHENTICATE USER │ ├─ 2. GET USER'S ORGANIZATION │ ├─ 3. QUERY DATABASE │ ├─ SELECT * FROM activity_log │ ├─ WHERE organization_id = ? │ ├─ ORDER BY created_at DESC │ └─ LIMIT 50 │ └─ DATABASE RESPONSE [ { "id": "event-uuid-1", "event_type": "document_processed", "event_title": "Engine Manual processed", "reference_type": "document", "reference_id": "doc-uuid-123", "user_id": "user-uuid-456", "created_at": 1699875600000, "userName": "John Smith" }, { "id": "event-uuid-2", "event_type": "document_uploaded", "event_title": "Wiring Diagram uploaded (245 pages)", "reference_type": "document", "reference_id": "doc-uuid-789", "user_id": "user-uuid-456", "created_at": 1699875480000, "userName": "John Smith" }, ... more events ] │ ▼ FRONTEND DISPLAYS TIMELINE │ ├─ Recent events at top ├─ Document icons for each event ├─ Timestamp ├─ User who performed action └─ "View document" link ``` --- ## Directory Tree: Production Deployment ``` /tmp/navidocs/ [EXECUTABLE - 300-400 MB] │ ├── server/ [Main application code] │ ├── index.js ← Entry point (run this) │ ├── package.json ← Dependencies │ ├── package-lock.json │ │ │ ├── config/ │ │ ├── db.js ← SQLite connection │ │ ├── paths.js ← Path configuration │ │ └── environment.js ← Environment setup │ │ │ ├── routes/ ← API endpoints │ │ ├── auth.routes.js ← Login/register │ │ ├── upload.js ← File upload │ │ ├── search.js ← Search API │ │ ├── documents.js ← Document management │ │ ├── timeline.js ← Activity log │ │ ├── quick-ocr.js ← OCR endpoint │ │ ├── jobs.js ← Job status │ │ └── ... [10+ more routes] │ │ │ ├── services/ ← Business logic │ │ ├── ocr.js ← Smart OCR engine │ │ ├── pdf-text-extractor.js ← Native PDF text │ │ ├── document-processor.js ← Multi-format handler │ │ ├── activity-logger.js ← Timeline events │ │ ├── search.js ← Meilisearch wrapper │ │ ├── database.js ← SQLite helpers │ │ ├── file-safety.js ← File validation │ │ └── ... [more services] │ │ │ ├── workers/ ← Background jobs │ │ └── ocr-worker.js ← OCR processing │ │ │ ├── middleware/ ← Express middleware │ │ ├── auth.js ← JWT verification │ │ ├── errorHandler.js ← Error handling │ │ └── ... [more middleware] │ │ │ ├── db/ │ │ ├── init.js ← Database initialization │ │ ├── schema.sql ← Table definitions │ │ └── migrations/ │ │ └── 010_activity_timeline.sql │ │ │ ├── utils/ │ │ ├── logger.js ← Logging utility │ │ ├── validators.js ← Input validation │ │ └── ... [helpers] │ │ │ └── node_modules/ [350+ npm packages] │ ├── express/ │ ├── pdfjs-dist/ │ ├── tesseract.js/ │ ├── meilisearch/ │ ├── sqlite3/ │ ├── bullmq/ │ ├── mammoth/ │ ├── xlsx/ │ └── ... [many more] │ ├── client/ [Frontend source] │ ├── package.json │ ├── vite.config.js ← Build config │ ├── tailwind.config.js ← Styles │ ├── src/ │ │ ├── main.js ← Entry point │ │ ├── App.vue ← Root component │ │ ├── router.js ← Vue Router │ │ ├── views/ │ │ │ ├── HomeView.vue │ │ │ ├── UploadView.vue │ │ │ ├── SearchView.vue │ │ │ └── TimelineView.vue │ │ ├── components/ │ │ │ ├── DocumentUpload.vue │ │ │ ├── SearchResults.vue │ │ │ └── TimelineEvent.vue │ │ └── stores/ │ │ └── search.js ← Pinia store │ └── dist/ [Output - deployed to web] │ ├── index.html │ ├── assets/ │ └── [static files] │ └── README.md ~/navidocs-data/ [PERSISTENT - starts ~10 MB] │ ├── .env [SECRETS - chmod 600] │ ├── PORT=8001 │ ├── NODE_ENV=production │ ├── DATABASE_PATH=~/navidocs-data/db/navidocs.db │ ├── JWT_SECRET=[64-char hex] │ ├── MEILISEARCH_MASTER_KEY=[32-char hex] │ ├── SETTINGS_ENCRYPTION_KEY=[64-char hex] │ └── [... more config ...] │ ├── db/ │ └── navidocs.db [SQLite database] │ ├── documents (table) │ │ ├── id (UUID primary key) │ │ ├── organization_id │ │ ├── title │ │ ├── file_name │ │ ├── file_path │ │ ├── file_size │ │ ├── mime_type │ │ ├── page_count │ │ ├── text_content │ │ ├── status (processing/ready/error) │ │ ├── created_at (timestamp) │ │ └── updated_at (timestamp) │ │ │ ├── users (table) │ │ ├── id │ │ ├── email │ │ ├── password_hash │ │ ├── name │ │ └── created_at │ │ │ ├── activity_log (table) │ │ ├── id │ │ ├── organization_id │ │ ├── user_id │ │ ├── event_type (upload/process/search/delete) │ │ ├── event_title │ │ ├── reference_type (document) │ │ ├── reference_id │ │ ├── created_at │ │ └── [indexed by created_at] │ │ │ └── [other tables for settings, permissions, etc.] │ ├── uploads/ [User-uploaded documents] │ ├── [uuid-1].pdf ← 45 MB PDF │ ├── [uuid-2].pdf ← 12 MB PDF │ ├── [uuid-3].jpg ← 3 MB image │ ├── [uuid-4].docx ← 2 MB Word doc │ ├── [uuid-5].xlsx ← 1 MB Excel sheet │ └── [etc - total ~100-300 MB] │ └── logs/ ├── app.log [Main application logs] │ ├── [2025-11-13 10:00:00] INFO Server started on port 8001 │ ├── [2025-11-13 10:00:05] INFO User login: user@example.com │ ├── [2025-11-13 10:00:10] INFO Document upload: manual.pdf (45 MB) │ ├── [2025-11-13 10:00:12] INFO OCR job queued: job-123 │ ├── [2025-11-13 10:00:20] INFO Search query: "bilge pump" (42 results) │ └── [etc - rotated daily/weekly] │ ├── ocr-worker.log [Background job logs] │ ├── [2025-11-13 10:00:12] INFO Job 123 started │ ├── [2025-11-13 10:00:15] INFO Extracting native text: page 1-100 │ ├── [2025-11-13 10:00:18] INFO OCR page 34: low confidence │ ├── [2025-11-13 10:00:20] INFO Job 123 completed in 8s │ └── [etc] │ └── error.log [Error log] ├── [2025-11-13 09:55:00] ERROR Failed to save file: disk space ├── [2025-11-13 09:56:00] ERROR OCR timeout: 5+ minutes └── [etc] ~/public_html/digital-lab.ca/navidocs/ [WEB-SERVED - ~10 MB] │ ├── index.html [Vue 3 app entry] │ └── Contains: ,