## Backend (server/) - Express 5 API with security middleware (helmet, rate limiting) - SQLite database with WAL mode (schema from docs/architecture/) - Meilisearch integration with tenant tokens - BullMQ + Redis background job queue - OCR pipeline with Tesseract.js - File safety validation (extension, MIME, size) - 4 API route modules: upload, jobs, search, documents ## Frontend (client/) - Vue 3 with Composition API (<script setup>) - Vite 5 build system with HMR - Tailwind CSS (Meilisearch-inspired design) - UploadModal with drag-and-drop - FigureZoom component (ported from lilian1) - Meilisearch search integration with tenant tokens - Job polling composable - Clean SVG icons (no emojis) ## Code Extraction - ✅ manuals.js → UploadModal.vue, useJobPolling.js - ✅ figure-zoom.js → FigureZoom.vue - ✅ service-worker.js → client/public/service-worker.js (TODO) - ✅ glossary.json → Merged into Meilisearch synonyms - ❌ Discarded: quiz.js, persona.js, gamification.js (Frank-AI junk) ## Documentation - Complete extraction plan in docs/analysis/ - README with quick start guide - Architecture summary in docs/architecture/ ## Build Status - Server dependencies: ✅ Installed (234 packages) - Client dependencies: ✅ Installed (160 packages) - Client build: ✅ Successful (2.63s) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
496 lines
9.5 KiB
Markdown
496 lines
9.5 KiB
Markdown
# NaviDocs API Routes
|
|
|
|
This directory contains the backend API route modules for NaviDocs server.
|
|
|
|
## Route Modules
|
|
|
|
### 1. Upload Route (`upload.js`)
|
|
**Endpoint:** `POST /api/upload`
|
|
|
|
Handles PDF file uploads with validation, storage, and OCR queue processing.
|
|
|
|
**Request:**
|
|
- Content-Type: `multipart/form-data`
|
|
- Body:
|
|
- `file`: PDF file (max 50MB)
|
|
- `title`: Document title (string, required)
|
|
- `documentType`: Document type (string, required)
|
|
- Values: `owner-manual`, `component-manual`, `service-record`, etc.
|
|
- `organizationId`: Organization UUID (string, required)
|
|
- `entityId`: Entity UUID (string, optional)
|
|
- `subEntityId`: Sub-entity UUID (string, optional)
|
|
- `componentId`: Component UUID (string, optional)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"jobId": "uuid",
|
|
"documentId": "uuid",
|
|
"message": "File uploaded successfully and queued for processing"
|
|
}
|
|
```
|
|
|
|
**Status Codes:**
|
|
- `201`: Created - File uploaded successfully
|
|
- `400`: Bad Request - Invalid file or missing fields
|
|
- `401`: Unauthorized - Authentication required
|
|
- `500`: Internal Server Error
|
|
|
|
**Security:**
|
|
- File extension validation (.pdf only)
|
|
- MIME type verification (magic number detection)
|
|
- File size limit (50MB default)
|
|
- Filename sanitization
|
|
- SHA256 hash for deduplication
|
|
|
|
---
|
|
|
|
### 2. Jobs Route (`jobs.js`)
|
|
**Endpoints:**
|
|
|
|
#### Get Job Status
|
|
`GET /api/jobs/:id`
|
|
|
|
Query OCR job status and progress.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"jobId": "uuid",
|
|
"documentId": "uuid",
|
|
"status": "pending|processing|completed|failed",
|
|
"progress": 0-100,
|
|
"error": "error message or null",
|
|
"startedAt": timestamp,
|
|
"completedAt": timestamp,
|
|
"createdAt": timestamp,
|
|
"document": {
|
|
"id": "uuid",
|
|
"status": "processing|indexed|failed",
|
|
"pageCount": 42
|
|
}
|
|
}
|
|
```
|
|
|
|
#### List Jobs
|
|
`GET /api/jobs`
|
|
|
|
List jobs with optional filtering.
|
|
|
|
**Query Parameters:**
|
|
- `status`: Filter by status (`pending`, `processing`, `completed`, `failed`)
|
|
- `limit`: Results per page (default: 50, max: 100)
|
|
- `offset`: Pagination offset (default: 0)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"jobs": [
|
|
{
|
|
"jobId": "uuid",
|
|
"documentId": "uuid",
|
|
"documentTitle": "Owner Manual",
|
|
"documentType": "owner-manual",
|
|
"status": "completed",
|
|
"progress": 100,
|
|
"error": null,
|
|
"startedAt": timestamp,
|
|
"completedAt": timestamp,
|
|
"createdAt": timestamp
|
|
}
|
|
],
|
|
"pagination": {
|
|
"limit": 50,
|
|
"offset": 0
|
|
}
|
|
}
|
|
```
|
|
|
|
**Status Codes:**
|
|
- `200`: OK
|
|
- `400`: Bad Request - Invalid job ID
|
|
- `401`: Unauthorized
|
|
- `404`: Not Found - Job not found
|
|
|
|
---
|
|
|
|
### 3. Search Route (`search.js`)
|
|
**Endpoints:**
|
|
|
|
#### Generate Tenant Token
|
|
`POST /api/search/token`
|
|
|
|
Generate Meilisearch tenant token for client-side search with 1-hour TTL.
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"expiresIn": 3600
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"token": "tenant-token-string",
|
|
"expiresAt": "2025-10-19T12:00:00.000Z",
|
|
"expiresIn": 3600,
|
|
"indexName": "navidocs-pages",
|
|
"searchUrl": "http://127.0.0.1:7700"
|
|
}
|
|
```
|
|
|
|
**Security:**
|
|
- Token scoped to user's organizations
|
|
- Row-level security via filters
|
|
- Maximum expiration: 24 hours
|
|
- Filters: `userId = X OR organizationId IN [Y, Z]`
|
|
|
|
#### Server-Side Search
|
|
`POST /api/search`
|
|
|
|
Perform server-side search (optional, for server-rendered results).
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"q": "search query",
|
|
"filters": {
|
|
"documentType": "owner-manual",
|
|
"entityId": "uuid",
|
|
"language": "en"
|
|
},
|
|
"limit": 20,
|
|
"offset": 0
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": "page-uuid",
|
|
"text": "highlighted text",
|
|
"pageNumber": 42,
|
|
"documentId": "uuid",
|
|
"documentTitle": "Owner Manual"
|
|
}
|
|
],
|
|
"estimatedTotalHits": 150,
|
|
"query": "search query",
|
|
"processingTimeMs": 12,
|
|
"limit": 20,
|
|
"offset": 0
|
|
}
|
|
```
|
|
|
|
#### Health Check
|
|
`GET /api/search/health`
|
|
|
|
Check Meilisearch connectivity.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"meilisearch": {
|
|
"status": "available"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Documents Route (`documents.js`)
|
|
**Endpoints:**
|
|
|
|
#### Get Document
|
|
`GET /api/documents/:id`
|
|
|
|
Query document metadata with ownership verification.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"organizationId": "uuid",
|
|
"entityId": "uuid",
|
|
"subEntityId": "uuid",
|
|
"componentId": "uuid",
|
|
"uploadedBy": "user-uuid",
|
|
"title": "Owner Manual",
|
|
"documentType": "owner-manual",
|
|
"fileName": "manual.pdf",
|
|
"fileSize": 1024000,
|
|
"mimeType": "application/pdf",
|
|
"pageCount": 42,
|
|
"language": "en",
|
|
"status": "indexed",
|
|
"createdAt": timestamp,
|
|
"updatedAt": timestamp,
|
|
"metadata": {},
|
|
"filePath": "/path/to/file.pdf",
|
|
"pages": [
|
|
{
|
|
"id": "page-uuid",
|
|
"pageNumber": 1,
|
|
"ocrConfidence": 0.95,
|
|
"ocrLanguage": "en",
|
|
"ocrCompletedAt": timestamp,
|
|
"searchIndexedAt": timestamp
|
|
}
|
|
],
|
|
"entity": {
|
|
"id": "uuid",
|
|
"name": "My Boat",
|
|
"entityType": "boat"
|
|
},
|
|
"component": {
|
|
"id": "uuid",
|
|
"name": "Main Engine",
|
|
"manufacturer": "Caterpillar",
|
|
"modelNumber": "C7.1"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Status Codes:**
|
|
- `200`: OK
|
|
- `400`: Bad Request - Invalid document ID
|
|
- `401`: Unauthorized
|
|
- `403`: Forbidden - No access to document
|
|
- `404`: Not Found
|
|
|
|
**Security:**
|
|
- Ownership verification
|
|
- Organization membership check
|
|
- Document share permissions
|
|
|
|
#### List Documents
|
|
`GET /api/documents`
|
|
|
|
List documents with filtering.
|
|
|
|
**Query Parameters:**
|
|
- `organizationId`: Filter by organization
|
|
- `entityId`: Filter by entity
|
|
- `documentType`: Filter by document type
|
|
- `status`: Filter by status
|
|
- `limit`: Results per page (default: 50)
|
|
- `offset`: Pagination offset (default: 0)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"documents": [
|
|
{
|
|
"id": "uuid",
|
|
"organizationId": "uuid",
|
|
"entityId": "uuid",
|
|
"title": "Owner Manual",
|
|
"documentType": "owner-manual",
|
|
"fileName": "manual.pdf",
|
|
"fileSize": 1024000,
|
|
"pageCount": 42,
|
|
"status": "indexed",
|
|
"createdAt": timestamp,
|
|
"updatedAt": timestamp
|
|
}
|
|
],
|
|
"pagination": {
|
|
"total": 150,
|
|
"limit": 50,
|
|
"offset": 0,
|
|
"hasMore": true
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Delete Document
|
|
`DELETE /api/documents/:id`
|
|
|
|
Soft delete a document (marks as deleted).
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Document deleted successfully",
|
|
"documentId": "uuid"
|
|
}
|
|
```
|
|
|
|
**Status Codes:**
|
|
- `200`: OK
|
|
- `401`: Unauthorized
|
|
- `403`: Forbidden - No permission to delete
|
|
- `404`: Not Found
|
|
|
|
**Permissions:**
|
|
- Document uploader
|
|
- Organization admin
|
|
- Organization manager
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
All routes require authentication via JWT token (except health checks).
|
|
|
|
**Header:**
|
|
```
|
|
Authorization: Bearer <jwt-token>
|
|
```
|
|
|
|
The authentication middleware attaches `req.user` with:
|
|
```javascript
|
|
{
|
|
id: "user-uuid",
|
|
email: "user@example.com",
|
|
name: "User Name"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
All routes follow consistent error response format:
|
|
|
|
```json
|
|
{
|
|
"error": "Error message",
|
|
"message": "Detailed error description"
|
|
}
|
|
```
|
|
|
|
**Common Status Codes:**
|
|
- `400`: Bad Request - Invalid input
|
|
- `401`: Unauthorized - Missing or invalid authentication
|
|
- `403`: Forbidden - Insufficient permissions
|
|
- `404`: Not Found - Resource not found
|
|
- `500`: Internal Server Error - Server error
|
|
|
|
---
|
|
|
|
## Database Schema
|
|
|
|
Routes use the database schema defined in `/server/db/schema.sql`:
|
|
|
|
**Tables:**
|
|
- `documents` - Document metadata
|
|
- `document_pages` - OCR results per page
|
|
- `ocr_jobs` - Background job queue
|
|
- `users` - User accounts
|
|
- `organizations` - Organizations
|
|
- `user_organizations` - Membership
|
|
- `entities` - Boats, marinas, condos
|
|
- `components` - Engines, panels, appliances
|
|
- `document_shares` - Sharing permissions
|
|
|
|
---
|
|
|
|
## Dependencies
|
|
|
|
**Services:**
|
|
- `db/db.js` - SQLite database connection
|
|
- `services/file-safety.js` - File validation
|
|
- `services/queue.js` - BullMQ job queue
|
|
- `config/meilisearch.js` - Meilisearch client
|
|
|
|
**External:**
|
|
- Meilisearch - Search engine (port 7700)
|
|
- Redis - Job queue backend (port 6379)
|
|
- SQLite - Database storage
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Upload Example
|
|
```bash
|
|
curl -X POST http://localhost:3001/api/upload \
|
|
-H "Authorization: Bearer <token>" \
|
|
-F "file=@manual.pdf" \
|
|
-F "title=Owner Manual" \
|
|
-F "documentType=owner-manual" \
|
|
-F "organizationId=<uuid>"
|
|
```
|
|
|
|
### Get Job Status
|
|
```bash
|
|
curl http://localhost:3001/api/jobs/<job-id> \
|
|
-H "Authorization: Bearer <token>"
|
|
```
|
|
|
|
### Generate Search Token
|
|
```bash
|
|
curl -X POST http://localhost:3001/api/search/token \
|
|
-H "Authorization: Bearer <token>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"expiresIn": 3600}'
|
|
```
|
|
|
|
### Get Document
|
|
```bash
|
|
curl http://localhost:3001/api/documents/<doc-id> \
|
|
-H "Authorization: Bearer <token>"
|
|
```
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
1. **File Validation**
|
|
- Extension check (.pdf only)
|
|
- MIME type verification (magic numbers)
|
|
- File size limits (50MB default)
|
|
- Filename sanitization
|
|
|
|
2. **Access Control**
|
|
- JWT authentication required
|
|
- Organization-based permissions
|
|
- Row-level security in Meilisearch
|
|
- Document sharing permissions
|
|
|
|
3. **Input Sanitization**
|
|
- UUID format validation
|
|
- SQL injection prevention (prepared statements)
|
|
- XSS prevention (no user input in HTML)
|
|
|
|
4. **Rate Limiting**
|
|
- 100 requests per 15 minutes per IP
|
|
- Configurable via environment variables
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
```env
|
|
# Server
|
|
PORT=3001
|
|
NODE_ENV=development
|
|
|
|
# Database
|
|
DATABASE_PATH=./db/navidocs.db
|
|
|
|
# Meilisearch
|
|
MEILISEARCH_HOST=http://127.0.0.1:7700
|
|
MEILISEARCH_MASTER_KEY=your-master-key-here
|
|
MEILISEARCH_INDEX_NAME=navidocs-pages
|
|
|
|
# Redis
|
|
REDIS_HOST=127.0.0.1
|
|
REDIS_PORT=6379
|
|
|
|
# Authentication
|
|
JWT_SECRET=your-jwt-secret-here
|
|
|
|
# File Upload
|
|
MAX_FILE_SIZE=52428800
|
|
UPLOAD_DIR=./uploads
|
|
|
|
# Rate Limiting
|
|
RATE_LIMIT_WINDOW_MS=900000
|
|
RATE_LIMIT_MAX_REQUESTS=100
|
|
```
|