const express = require('express'); const cors = require('cors'); const path = require('path'); const fs = require('fs'); const db = require('./db'); const scanner = require('./scanner'); const ai = require('./ai'); const app = express(); const PORT = process.env.PORT || 4000; // Middleware app.use(cors()); app.use(express.json()); // Serve static files from public directory app.use(express.static(path.join(__dirname, 'public'))); // Create public directory if it doesn't exist const publicDir = path.join(__dirname, 'public'); if (!fs.existsSync(publicDir)) { fs.mkdirSync(publicDir, { recursive: true }); } // Track scanning state let lastRunTime = null; let scanProgress = { current: 0, total: 0, profileName: '' }; // API Routes // --- PROFILES --- app.get('/api/profiles', (req, res) => { try { let profiles = db.getProfiles(); if (profiles.length === 0) { // Create a default profile if none exists db.addProfile('Default Profile', 0.85); profiles = db.getProfiles(); } res.json(profiles); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/profiles', (req, res) => { try { const { name, price_ratio } = req.body; db.addProfile(name, price_ratio || 0.85); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); app.put('/api/profiles/:id', (req, res) => { try { const { name, price_ratio } = req.body; db.updateProfile(req.params.id, name, price_ratio); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); app.delete('/api/profiles/:id', (req, res) => { try { db.deleteProfile(req.params.id); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); // --- KEYWORDS --- app.get('/api/profiles/:id/keywords', (req, res) => { try { const keywords = db.getKeywords(req.params.id); res.json(keywords); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/profiles/:id/keywords', (req, res) => { try { const { part_number, keywords, target_price } = req.body; db.addKeyword(req.params.id, part_number, keywords, target_price); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); app.put('/api/keywords/:id', (req, res) => { try { const { part_number, keywords, target_price } = req.body; db.updateKeyword(req.params.id, part_number, keywords, target_price); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/profiles/:id/keywords/bulk', (req, res) => { try { const { items } = req.body; // Array of {part_number, keywords, target_price} for (const item of items) { db.addKeyword(req.params.id, item.part_number, item.keywords, item.target_price); } res.json({ success: true, count: items.length }); } catch (err) { res.status(500).json({ error: err.message }); } }); app.delete('/api/keywords/:id', (req, res) => { try { db.deleteKeyword(req.params.id); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); // --- ITEMS --- // Get scan status app.get('/api/status', (req, res) => { res.json({ isScanning, lastRunTime, scanProgress }); }); // Get all "waiting" PASS items for a profile app.get('/api/items', (req, res) => { try { const { profile_id } = req.query; const items = db.getWaitingPassItems(profile_id); res.json(items); } catch (err) { console.error('Error fetching items:', err); res.status(500).json({ error: 'Internal server error' }); } }); // Update item review status app.put('/api/items/:id/status', (req, res) => { try { const { id } = req.params; const { status } = req.body; if (!['waiting', 'done', 'skip'].includes(status)) { return res.status(400).json({ error: 'Invalid status' }); } db.updateReviewStatus(id, status); res.json({ success: true, message: `Status updated to ${status}` }); } catch (err) { console.error(`Error updating status for item ${req.params.id}:`, err); res.status(500).json({ error: 'Internal server error' }); } }); // Trigger a new scan app.post('/api/scan', async (req, res) => { if (isScanning) { return res.status(400).json({ error: 'Scan is already running' }); } const { profile_id } = req.body; if (!profile_id) { return res.status(400).json({ error: 'profile_id is required' }); } isScanning = true; res.json({ success: true, message: 'Scan started in background' }); try { console.log(`--- STARTING BACKGROUND SCAN FOR PROFILE ${profile_id} ---`); scanProgress = { current: 0, total: 0, profileName: '' }; await scanner.runScannerCore(profile_id, (current, total, profileName) => { scanProgress = { current, total, profileName }; }); console.log('--- BACKGROUND SCAN FINISHED ---'); } catch (err) { console.error('Error running scan:', err); } finally { isScanning = false; lastRunTime = new Date().toISOString(); scanProgress = { current: 0, total: 0, profileName: '' }; } }); // Trigger AI on specific item app.post('/api/items/:id/ai', async (req, res) => { try { const { id } = req.params; const item = db.getItem(id); if (!item) { return res.status(404).json({ error: 'Item not found' }); } // Convert JSON strings back to objects for AI prompt item.detail_response = item.detail_response ? JSON.parse(item.detail_response) : null; const suggestion = await ai.getAiSuggestion(item); db.updateAiSuggestion(id, suggestion); res.json({ success: true, ai_suggestion: suggestion }); } catch (err) { console.error(`Error requesting AI for item ${req.params.id}:`, err); res.status(500).json({ error: 'Internal server error' }); } }); // Default catch-all to index.html for SPA feel app.use((req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); // Start server app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });