226 lines
6.2 KiB
JavaScript
226 lines
6.2 KiB
JavaScript
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 isScanning = false;
|
|
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, null);
|
|
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, common_keywords } = req.body;
|
|
db.addProfile(name, price_ratio || 0.85, common_keywords);
|
|
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, common_keywords } = req.body;
|
|
db.updateProfile(req.params.id, name, price_ratio, common_keywords);
|
|
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}`);
|
|
});
|