## 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>
109 lines
2.7 KiB
JavaScript
109 lines
2.7 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 jobsRoutes from './routes/jobs.js';
|
|
import searchRoutes from './routes/search.js';
|
|
import documentsRoutes from './routes/documents.js';
|
|
|
|
// API routes
|
|
app.use('/api/upload', uploadRoutes);
|
|
app.use('/api/jobs', jobsRoutes);
|
|
app.use('/api/search', searchRoutes);
|
|
app.use('/api/documents', documentsRoutes);
|
|
|
|
// 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;
|