navidocs/server/index.js
ggq-admin d461c5742f Fix search, add PDF text selection, clean duplicates, implement auto-fill
This commit addresses multiple critical fixes and adds new functionality
for the NaviDocs local testing environment (port 8083):

Search Fixes:
- Fixed search to use backend /api/search instead of direct Meilisearch
- Resolves network accessibility issue when accessing from external IPs
- Search now works from http://172.29.75.55:8083/search

PDF Text Selection:
- Added PDF.js text layer for selectable text
- Imported pdf_viewer.css for proper text layer styling
- Changed text layer opacity to 1 for better interaction
- Added user-select: text for improved text selection
- Pink selection highlight (rgba(255, 92, 178, 0.3))

Database Cleanup:
- Created cleanup scripts to remove 20 duplicate documents
- Removed 753 orphaned entries from Meilisearch index
- Cleaned 17 document folders from filesystem
- Kept only newest version of each document
- Scripts: clean-duplicates.js, clean-meilisearch-orphans.js

Auto-Fill Feature:
- New /api/upload/quick-ocr endpoint for first-page OCR
- Automatically extracts metadata from PDFs on file selection
- Detects: boat make, model, year, name, and document title
- Checks both OCR text and filename for boat name
- Auto-fills upload form with extracted data
- Shows loading indicator during metadata extraction
- Graceful fallback to filename if OCR fails

Tenant Management:
- Updated organization ID to use boat name as tenant
- Falls back to "Liliane 1" for single-tenant setup
- Each boat becomes a unique tenant in the system

Files Changed:
- client/src/views/DocumentView.vue - Text layer implementation
- client/src/composables/useSearch.js - Backend API integration
- client/src/components/UploadModal.vue - Auto-fill feature
- server/routes/quick-ocr.js - OCR endpoint (new)
- server/index.js - Route registration
- server/scripts/* - Cleanup utilities (new)

Testing:
All features tested on local deployment at http://172.29.75.55:8083
- Backend: http://localhost:8001
- Frontend: http://localhost:8083
- Meilisearch: http://localhost:7700

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 01:35:06 +02:00

113 lines
2.9 KiB
JavaScript

/**
* NaviDocs Backend API
* Express server with SQLite + Meilisearch
*/
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
// Load environment variables
dotenv.config();
const __dirname = dirname(fileURLToPath(import.meta.url));
const PORT = process.env.PORT || 3001;
const NODE_ENV = process.env.NODE_ENV || 'development';
// Create Express app
const app = express();
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'blob:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
},
crossOriginEmbedderPolicy: false
}));
// CORS
app.use(cors({
origin: NODE_ENV === 'production' ? process.env.ALLOWED_ORIGINS?.split(',') : '*',
credentials: true
}));
// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Rate limiting
const limiter = rateLimit({
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000'), // 15 minutes
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100'),
standardHeaders: true,
legacyHeaders: false,
message: 'Too many requests, please try again later'
});
app.use('/api/', limiter);
// Health check
app.get('/health', async (req, res) => {
try {
// TODO: Check database, Meilisearch, queue
res.json({
status: 'ok',
timestamp: Date.now(),
uptime: process.uptime()
});
} catch (error) {
res.status(500).json({
status: 'error',
error: error.message
});
}
});
// Import route modules
import uploadRoutes from './routes/upload.js';
import quickOcrRoutes from './routes/quick-ocr.js';
import jobsRoutes from './routes/jobs.js';
import searchRoutes from './routes/search.js';
import documentsRoutes from './routes/documents.js';
import imagesRoutes from './routes/images.js';
// API routes
app.use('/api/upload/quick-ocr', quickOcrRoutes);
app.use('/api/upload', uploadRoutes);
app.use('/api/jobs', jobsRoutes);
app.use('/api/search', searchRoutes);
app.use('/api/documents', documentsRoutes);
app.use('/api', imagesRoutes);
// Error handling
app.use((err, req, res, next) => {
console.error('Error:', err);
res.status(err.status || 500).json({
error: err.message || 'Internal server error',
...(NODE_ENV === 'development' && { stack: err.stack })
});
});
// Start server
app.listen(PORT, () => {
console.log(`NaviDocs API listening on port ${PORT}`);
console.log(`Environment: ${NODE_ENV}`);
console.log(`Health check: http://localhost:${PORT}/health`);
});
export default app;