manage_ram_scanner/server.js

342 lines
9.7 KiB
JavaScript

const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
const app = express();
// Middleware
app.use(express.json());
app.use(express.static('public'));
// CORS middleware
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
// Initialize SQLite database
const db = new sqlite3.Database('./products.db', (err) => {
if (err) console.error(err.message);
console.log('Connected to products database.');
});
// Create tables
db.serialize(() => {
// Batches table
db.run(`CREATE TABLE IF NOT EXISTS batches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
batch_name TEXT NOT NULL UNIQUE,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
// Items table (for valid items)
db.run(`CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
batch_id INTEGER NOT NULL,
mpn TEXT NOT NULL,
sn TEXT NOT NULL,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (batch_id) REFERENCES batches(id) ON DELETE CASCADE
)`);
// Items_mix table (for mixed MPN items)
db.run(`CREATE TABLE IF NOT EXISTS items_mix (
id INTEGER PRIMARY KEY AUTOINCREMENT,
batch_id INTEGER NOT NULL,
mpn TEXT NOT NULL,
sn TEXT NOT NULL,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (batch_id) REFERENCES batches(id) ON DELETE CASCADE
)`);
// Create indexes for better performance
db.run(`CREATE INDEX IF NOT EXISTS idx_items_batch_id ON items(batch_id)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_items_mix_batch_id ON items_mix(batch_id)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_items_mpn ON items(mpn)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_items_sn ON items(sn)`);
});
// ==================== BATCH API ROUTES ====================
// Save batch with items and items_mix
app.post('/api/batch/save', (req, res) => {
const { batch_name, items, items_mix } = req.body;
if (!batch_name) {
return res.status(400).json({ error: 'batch_name is required' });
}
if (!Array.isArray(items) || items.length === 0) {
return res.status(400).json({ error: 'items array is required and must not be empty' });
}
db.serialize(() => {
db.run('BEGIN TRANSACTION');
// Insert batch
db.run('INSERT INTO batches (batch_name) VALUES (?)', [batch_name], function(err) {
if (err) {
db.run('ROLLBACK');
return res.status(500).json({ error: 'Batch name already exists or database error: ' + err.message });
}
const batchId = this.lastID;
let insertedItems = 0;
let insertedMixItems = 0;
let errors = [];
// Insert items
const itemStmt = db.prepare('INSERT INTO items (batch_id, mpn, sn) VALUES (?, ?, ?)');
items.forEach((item, index) => {
if (!item.mpn || !item.sn) {
errors.push(`Item at index ${index} is missing mpn or sn`);
return;
}
itemStmt.run(batchId, item.mpn, item.sn, (err) => {
if (err) errors.push(`Error inserting item at index ${index}: ${err.message}`);
else insertedItems++;
});
});
itemStmt.finalize();
// Insert items_mix if provided
if (Array.isArray(items_mix) && items_mix.length > 0) {
const mixStmt = db.prepare('INSERT INTO items_mix (batch_id, mpn, sn) VALUES (?, ?, ?)');
items_mix.forEach((item, index) => {
if (!item.mpn || !item.sn) {
errors.push(`Mixed item at index ${index} is missing mpn or sn`);
return;
}
mixStmt.run(batchId, item.mpn, item.sn, (err) => {
if (err) errors.push(`Error inserting mixed item at index ${index}: ${err.message}`);
else insertedMixItems++;
});
});
mixStmt.finalize();
}
db.run('COMMIT', (err) => {
if (err) {
db.run('ROLLBACK');
return res.status(500).json({ error: 'Transaction failed: ' + err.message });
}
res.json({
success: true,
batch_id: batchId,
batch_name,
inserted_items: insertedItems,
inserted_mix_items: insertedMixItems,
errors: errors.length > 0 ? errors : undefined
});
});
});
});
});
// Get all batches with their items and items_mix
app.get('/api/batch/get-all', (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 50;
const search = req.query.search || '';
const sortBy = req.query.sortBy || 'id';
const sortOrder = req.query.sortOrder || 'DESC';
const offset = (page - 1) * limit;
// Validate sortBy
const validColumns = ['id', 'batch_name', 'createdAt'];
const column = validColumns.includes(sortBy) ? sortBy : 'id';
const order = sortOrder.toUpperCase() === 'ASC' ? 'ASC' : 'DESC';
let query = 'SELECT * FROM batches';
let countQuery = 'SELECT COUNT(*) as total FROM batches';
let params = [];
if (search) {
const searchCondition = ` WHERE batch_name LIKE ? OR id LIKE ?`;
query += searchCondition;
countQuery += searchCondition;
const searchParam = `%${search}%`;
params = [searchParam, searchParam];
}
query += ` ORDER BY ${column} ${order} LIMIT ? OFFSET ?`;
params.push(limit, offset);
db.get(countQuery, search ? params.slice(0, 2) : [], (err, countRow) => {
if (err) {
return res.status(500).json({ error: err.message });
}
db.all(query, params, (err, batches) => {
if (err) {
return res.status(500).json({ error: err.message });
}
if (batches.length === 0) {
return res.json({
batches: [],
total: 0,
page,
limit,
totalPages: 0
});
}
// Get items for each batch
let processed = 0;
const batchesWithItems = [];
batches.forEach(batch => {
// Get items
db.all('SELECT mpn, sn, createdAt FROM items WHERE batch_id = ?', [batch.id], (err, items) => {
if (err) {
console.error('Error fetching items:', err);
items = [];
}
// Get items_mix
db.all('SELECT mpn, sn, createdAt FROM items_mix WHERE batch_id = ?', [batch.id], (err, items_mix) => {
if (err) {
console.error('Error fetching items_mix:', err);
items_mix = [];
}
batchesWithItems.push({
id: batch.id,
batch_name: batch.batch_name,
createdAt: batch.createdAt,
items: items || [],
items_mix: items_mix || []
});
processed++;
if (processed === batches.length) {
res.json({
batches: batchesWithItems,
total: countRow.total,
page,
limit,
totalPages: Math.ceil(countRow.total / limit)
});
}
});
});
});
});
});
});
// Get single batch by ID
app.get('/api/batch/get/:id', (req, res) => {
const id = req.params.id;
db.get('SELECT * FROM batches WHERE id = ?', [id], (err, batch) => {
if (err) {
return res.status(500).json({ error: err.message });
}
if (!batch) {
return res.status(404).json({ error: 'Batch not found' });
}
// Get items
db.all('SELECT mpn, sn, createdAt FROM items WHERE batch_id = ?', [id], (err, items) => {
if (err) {
return res.status(500).json({ error: err.message });
}
// Get items_mix
db.all('SELECT mpn, sn, createdAt FROM items_mix WHERE batch_id = ?', [id], (err, items_mix) => {
if (err) {
return res.status(500).json({ error: err.message });
}
res.json({
id: batch.id,
batch_name: batch.batch_name,
createdAt: batch.createdAt,
items: items || [],
items_mix: items_mix || []
});
});
});
});
});
// Delete batch (cascade delete items and items_mix)
app.delete('/api/batch/delete/:id', (req, res) => {
const id = req.params.id;
db.run('DELETE FROM batches WHERE id = ?', id, function(err) {
if (err) {
return res.status(500).json({ error: err.message });
}
if (this.changes === 0) {
return res.status(404).json({ error: 'Batch not found' });
}
res.json({ success: true, deleted: this.changes });
});
});
// Search items across all batches
app.get('/api/items/search', (req, res) => {
const search = req.query.q || '';
if (!search) {
return res.status(400).json({ error: 'Search query is required' });
}
const searchParam = `%${search}%`;
const query = `
SELECT
b.id as batch_id,
b.batch_name,
i.mpn,
i.sn,
i.createdAt,
'items' as type
FROM items i
JOIN batches b ON i.batch_id = b.id
WHERE i.mpn LIKE ? OR i.sn LIKE ?
UNION ALL
SELECT
b.id as batch_id,
b.batch_name,
im.mpn,
im.sn,
im.createdAt,
'items_mix' as type
FROM items_mix im
JOIN batches b ON im.batch_id = b.id
WHERE im.mpn LIKE ? OR im.sn LIKE ?
ORDER BY createdAt DESC
LIMIT 100
`;
db.all(query, [searchParam, searchParam, searchParam, searchParam], (err, results) => {
if (err) {
return res.status(500).json({ error: err.message });
}
res.json({ results });
});
});
// Serve HTML UI
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
const PORT = process.env.PORT || 4444;
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});