Spaces:
Sleeping
Sleeping
| import express from 'express'; | |
| import multer from 'multer'; | |
| import path from 'path'; | |
| import fs from 'fs'; | |
| import { database } from '../db/database'; | |
| import { AuthenticatedRequest } from '../middleware/auth'; | |
| const router = express.Router(); | |
| // Configure multer for file uploads | |
| const storage = multer.diskStorage({ | |
| destination: (req, file, cb) => { | |
| const uploadDir = process.env.UPLOAD_DIR || './uploads'; | |
| if (!fs.existsSync(uploadDir)) { | |
| fs.mkdirSync(uploadDir, { recursive: true }); | |
| } | |
| cb(null, uploadDir); | |
| }, | |
| filename: (req, file, cb) => { | |
| const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); | |
| cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); | |
| } | |
| }); | |
| const upload = multer({ | |
| storage, | |
| limits: { | |
| fileSize: parseInt(process.env.MAX_FILE_SIZE || '10485760') // 10MB default | |
| }, | |
| fileFilter: (req, file, cb) => { | |
| const allowedTypes = ['.pdf', '.docx', '.txt', '.md']; | |
| const ext = path.extname(file.originalname).toLowerCase(); | |
| if (allowedTypes.includes(ext)) { | |
| cb(null, true); | |
| } else { | |
| cb(new Error('Invalid file type. Only PDF, DOCX, TXT, and MD files are allowed.')); | |
| } | |
| } | |
| }); | |
| // Get knowledge base documents | |
| router.get('/', async (req: AuthenticatedRequest, res) => { | |
| try { | |
| const { tenantId } = req.user!; | |
| const documents = await database.query( | |
| 'SELECT id, name, type, source, status, size, created_at, updated_at FROM knowledge_base WHERE tenant_id = ? ORDER BY created_at DESC', | |
| [tenantId] | |
| ); | |
| res.json({ documents }); | |
| } catch (error) { | |
| console.error('Get knowledge base error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // Upload document | |
| router.post('/upload', upload.single('document'), async (req: AuthenticatedRequest, res) => { | |
| try { | |
| const { tenantId } = req.user!; | |
| if (!req.file) { | |
| return res.status(400).json({ error: 'No file uploaded' }); | |
| } | |
| const { originalname, filename, size, mimetype } = req.file; | |
| const fileType = path.extname(originalname).toLowerCase().substring(1); | |
| // Save to database | |
| const result = await database.run( | |
| 'INSERT INTO knowledge_base (tenant_id, name, type, source, status, size, metadata) VALUES (?, ?, ?, ?, ?, ?, ?)', | |
| [ | |
| tenantId, | |
| originalname, | |
| fileType, | |
| filename, | |
| 'processing', | |
| size, | |
| JSON.stringify({ mimetype, uploadedAt: new Date().toISOString() }) | |
| ] | |
| ); | |
| // Log analytics event | |
| await database.run( | |
| 'INSERT INTO analytics_events (tenant_id, event_type, event_data) VALUES (?, ?, ?)', | |
| [tenantId, 'document_uploaded', JSON.stringify({ | |
| documentId: result.lastID, | |
| type: fileType, | |
| size | |
| })] | |
| ); | |
| res.status(201).json({ | |
| id: result.lastID, | |
| name: originalname, | |
| type: fileType, | |
| status: 'processing', | |
| message: 'Document uploaded successfully' | |
| }); | |
| } catch (error) { | |
| console.error('Upload document error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // Add website URL | |
| router.post('/url', async (req: AuthenticatedRequest, res) => { | |
| try { | |
| const { tenantId } = req.user!; | |
| const { url, name } = req.body; | |
| if (!url) { | |
| return res.status(400).json({ error: 'URL is required' }); | |
| } | |
| // Basic URL validation | |
| try { | |
| new URL(url); | |
| } catch { | |
| return res.status(400).json({ error: 'Invalid URL format' }); | |
| } | |
| const displayName = name || new URL(url).hostname; | |
| // Save to database | |
| const result = await database.run( | |
| 'INSERT INTO knowledge_base (tenant_id, name, type, source, status, metadata) VALUES (?, ?, ?, ?, ?, ?)', | |
| [ | |
| tenantId, | |
| displayName, | |
| 'website', | |
| url, | |
| 'processing', | |
| JSON.stringify({ addedAt: new Date().toISOString() }) | |
| ] | |
| ); | |
| // Log analytics event | |
| await database.run( | |
| 'INSERT INTO analytics_events (tenant_id, event_type, event_data) VALUES (?, ?, ?)', | |
| [tenantId, 'website_added', JSON.stringify({ | |
| documentId: result.lastID, | |
| url | |
| })] | |
| ); | |
| res.status(201).json({ | |
| id: result.lastID, | |
| name: displayName, | |
| type: 'website', | |
| source: url, | |
| status: 'processing', | |
| message: 'Website added successfully' | |
| }); | |
| } catch (error) { | |
| console.error('Add URL error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // Delete document | |
| router.delete('/:documentId', async (req: AuthenticatedRequest, res) => { | |
| try { | |
| const { tenantId } = req.user!; | |
| const { documentId } = req.params; | |
| // Get document info | |
| const document = await database.get( | |
| 'SELECT * FROM knowledge_base WHERE id = ? AND tenant_id = ?', | |
| [documentId, tenantId] | |
| ); | |
| if (!document) { | |
| return res.status(404).json({ error: 'Document not found' }); | |
| } | |
| // Delete file if it's a uploaded document | |
| if (document.type !== 'website') { | |
| const filePath = path.join(process.env.UPLOAD_DIR || './uploads', document.source); | |
| if (fs.existsSync(filePath)) { | |
| fs.unlinkSync(filePath); | |
| } | |
| } | |
| // Delete from database | |
| await database.run( | |
| 'DELETE FROM knowledge_base WHERE id = ? AND tenant_id = ?', | |
| [documentId, tenantId] | |
| ); | |
| // Log analytics event | |
| await database.run( | |
| 'INSERT INTO analytics_events (tenant_id, event_type, event_data) VALUES (?, ?, ?)', | |
| [tenantId, 'document_deleted', JSON.stringify({ | |
| documentId, | |
| name: document.name, | |
| type: document.type | |
| })] | |
| ); | |
| res.json({ message: 'Document deleted successfully' }); | |
| } catch (error) { | |
| console.error('Delete document error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // Update document status (for processing updates) | |
| router.put('/:documentId/status', async (req: AuthenticatedRequest, res) => { | |
| try { | |
| const { tenantId } = req.user!; | |
| const { documentId } = req.params; | |
| const { status } = req.body; | |
| if (!['processing', 'active', 'error'].includes(status)) { | |
| return res.status(400).json({ error: 'Invalid status' }); | |
| } | |
| await database.run( | |
| 'UPDATE knowledge_base SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND tenant_id = ?', | |
| [status, documentId, tenantId] | |
| ); | |
| res.json({ message: 'Document status updated successfully' }); | |
| } catch (error) { | |
| console.error('Update document status error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // Get document by ID | |
| router.get('/:documentId', async (req: AuthenticatedRequest, res) => { | |
| try { | |
| const { tenantId } = req.user!; | |
| const { documentId } = req.params; | |
| const document = await database.get( | |
| 'SELECT * FROM knowledge_base WHERE id = ? AND tenant_id = ?', | |
| [documentId, tenantId] | |
| ); | |
| if (!document) { | |
| return res.status(404).json({ error: 'Document not found' }); | |
| } | |
| res.json({ | |
| ...document, | |
| metadata: JSON.parse(document.metadata || '{}') | |
| }); | |
| } catch (error) { | |
| console.error('Get document error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| export default router; |