/** * API Routes for Test Data Database */ const express = require('express'); const path = require('path'); const Database = require('better-sqlite3'); const { generateDatasheet } = require('../templates/datasheet'); const router = express.Router(); // Database connection const DB_PATH = path.join(__dirname, '..', 'database', 'testdata.db'); function getDb() { return new Database(DB_PATH, { readonly: true }); } /** * GET /api/search * Search test records * Query params: serial, model, from, to, result, q, station, logtype, limit, offset */ router.get('/search', (req, res) => { try { const db = getDb(); const { serial, model, from, to, result, q, station, logtype, limit = 100, offset = 0 } = req.query; let sql = 'SELECT * FROM test_records WHERE 1=1'; const params = []; if (serial) { sql += ' AND serial_number LIKE ?'; params.push(serial.includes('%') ? serial : `%${serial}%`); } if (model) { sql += ' AND model_number LIKE ?'; params.push(model.includes('%') ? model : `%${model}%`); } if (from) { sql += ' AND test_date >= ?'; params.push(from); } if (to) { sql += ' AND test_date <= ?'; params.push(to); } if (result) { sql += ' AND overall_result = ?'; params.push(result.toUpperCase()); } if (station) { sql += ' AND test_station = ?'; params.push(station); } if (logtype) { sql += ' AND log_type = ?'; params.push(logtype); } if (q) { // Full-text search - rebuild query with FTS sql = `SELECT test_records.* FROM test_records JOIN test_records_fts ON test_records.id = test_records_fts.rowid WHERE test_records_fts MATCH ?`; params.length = 0; params.push(q); if (serial) { sql += ' AND serial_number LIKE ?'; params.push(serial.includes('%') ? serial : `%${serial}%`); } if (model) { sql += ' AND model_number LIKE ?'; params.push(model.includes('%') ? model : `%${model}%`); } if (station) { sql += ' AND test_station = ?'; params.push(station); } if (logtype) { sql += ' AND log_type = ?'; params.push(logtype); } if (result) { sql += ' AND overall_result = ?'; params.push(result.toUpperCase()); } if (from) { sql += ' AND test_date >= ?'; params.push(from); } if (to) { sql += ' AND test_date <= ?'; params.push(to); } } sql += ' ORDER BY test_date DESC, serial_number'; sql += ` LIMIT ? OFFSET ?`; params.push(parseInt(limit), parseInt(offset)); const records = db.prepare(sql).all(...params); // Get total count let countSql = sql.replace(/SELECT .* FROM/, 'SELECT COUNT(*) as count FROM') .replace(/ORDER BY.*$/, ''); countSql = countSql.replace(/LIMIT \? OFFSET \?/, ''); const countParams = params.slice(0, -2); const total = db.prepare(countSql).get(...countParams); db.close(); res.json({ records, total: total?.count || records.length, limit: parseInt(limit), offset: parseInt(offset) }); } catch (err) { res.status(500).json({ error: err.message }); } }); /** * GET /api/record/:id * Get single record by ID */ router.get('/record/:id', (req, res) => { try { const db = getDb(); const record = db.prepare('SELECT * FROM test_records WHERE id = ?').get(req.params.id); db.close(); if (!record) { return res.status(404).json({ error: 'Record not found' }); } res.json(record); } catch (err) { res.status(500).json({ error: err.message }); } }); /** * GET /api/datasheet/:id * Generate datasheet for a record * Query params: format (html, txt) */ router.get('/datasheet/:id', (req, res) => { try { const db = getDb(); const record = db.prepare('SELECT * FROM test_records WHERE id = ?').get(req.params.id); db.close(); if (!record) { return res.status(404).json({ error: 'Record not found' }); } const format = req.query.format || 'html'; const datasheet = generateDatasheet(record, format); if (format === 'html') { res.type('html').send(datasheet); } else { res.type('text/plain').send(datasheet); } } catch (err) { res.status(500).json({ error: err.message }); } }); /** * GET /api/stats * Get database statistics */ router.get('/stats', (req, res) => { try { const db = getDb(); const stats = { total_records: db.prepare('SELECT COUNT(*) as count FROM test_records').get().count, by_log_type: db.prepare(` SELECT log_type, COUNT(*) as count FROM test_records GROUP BY log_type ORDER BY count DESC `).all(), by_result: db.prepare(` SELECT overall_result, COUNT(*) as count FROM test_records GROUP BY overall_result `).all(), by_station: db.prepare(` SELECT test_station, COUNT(*) as count FROM test_records WHERE test_station IS NOT NULL AND test_station != '' GROUP BY test_station ORDER BY test_station `).all(), date_range: db.prepare(` SELECT MIN(test_date) as oldest, MAX(test_date) as newest FROM test_records `).get(), recent_serials: db.prepare(` SELECT DISTINCT serial_number, model_number, test_date FROM test_records ORDER BY test_date DESC LIMIT 10 `).all() }; db.close(); res.json(stats); } catch (err) { res.status(500).json({ error: err.message }); } }); /** * GET /api/filters * Get available filter options (test stations, log types, models) */ router.get('/filters', (req, res) => { try { const db = getDb(); const filters = { stations: db.prepare(` SELECT DISTINCT test_station FROM test_records WHERE test_station IS NOT NULL AND test_station != '' ORDER BY test_station `).all().map(r => r.test_station), log_types: db.prepare(` SELECT DISTINCT log_type FROM test_records ORDER BY log_type `).all().map(r => r.log_type), models: db.prepare(` SELECT DISTINCT model_number, COUNT(*) as count FROM test_records GROUP BY model_number ORDER BY count DESC LIMIT 500 `).all() }; db.close(); res.json(filters); } catch (err) { res.status(500).json({ error: err.message }); } }); /** * GET /api/export * Export search results as CSV */ router.get('/export', (req, res) => { try { const db = getDb(); const { serial, model, from, to, result, station, logtype } = req.query; let sql = 'SELECT * FROM test_records WHERE 1=1'; const params = []; if (serial) { sql += ' AND serial_number LIKE ?'; params.push(serial.includes('%') ? serial : `%${serial}%`); } if (model) { sql += ' AND model_number LIKE ?'; params.push(model.includes('%') ? model : `%${model}%`); } if (from) { sql += ' AND test_date >= ?'; params.push(from); } if (to) { sql += ' AND test_date <= ?'; params.push(to); } if (result) { sql += ' AND overall_result = ?'; params.push(result.toUpperCase()); } if (station) { sql += ' AND test_station = ?'; params.push(station); } if (logtype) { sql += ' AND log_type = ?'; params.push(logtype); } sql += ' ORDER BY test_date DESC, serial_number LIMIT 10000'; const records = db.prepare(sql).all(...params); db.close(); // Generate CSV const headers = ['id', 'log_type', 'model_number', 'serial_number', 'test_date', 'test_station', 'overall_result', 'source_file']; let csv = headers.join(',') + '\n'; for (const record of records) { const row = headers.map(h => { const val = record[h] || ''; return `"${String(val).replace(/"/g, '""')}"`; }); csv += row.join(',') + '\n'; } res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Disposition', 'attachment; filename=test_records.csv'); res.send(csv); } catch (err) { res.status(500).json({ error: err.message }); } }); module.exports = router;