← back to Astek Import Viewer
server.js
132 lines
'use strict';
const express = require('express');
const fs = require('fs');
const path = require('path');
const net = require('net');
const DATA_DIR = path.join(__dirname, 'data');
const DATA_FILE = path.join(DATA_DIR, 'astek.json');
const SELECTIONS_FILE = path.join(DATA_DIR, 'selections.json');
const PUBLIC_DIR = path.join(__dirname, 'public');
const PREFERRED_PORT = 9789;
const PORT_RANGE_END = 9799; // scan 9789..9799 for a free 97xx port
// ---- Load catalog once into memory ----
let PRODUCTS = [];
try {
PRODUCTS = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
if (!Array.isArray(PRODUCTS)) throw new Error('astek.json is not an array');
console.log(`Loaded ${PRODUCTS.length} products from ${DATA_FILE}`);
} catch (err) {
console.error(`FATAL: could not load ${DATA_FILE}:`, err.message);
process.exit(1);
}
// ---- Selections persistence helpers ----
function readSelections() {
try {
const raw = fs.readFileSync(SELECTIONS_FILE, 'utf8');
const arr = JSON.parse(raw);
return Array.isArray(arr) ? arr : [];
} catch (_) {
return [];
}
}
function writeSelections(ids) {
const clean = Array.from(new Set((Array.isArray(ids) ? ids : []))).filter(
(v) => v !== null && v !== undefined
);
fs.writeFileSync(SELECTIONS_FILE, JSON.stringify(clean, null, 2));
return clean;
}
function csvCell(v) {
if (v === null || v === undefined) return '';
const s = String(v);
if (/[",\n]/.test(s)) return '"' + s.replace(/"/g, '""') + '"';
return s;
}
// ---- App ----
const app = express();
app.use(express.json({ limit: '5mb' }));
app.use(express.static(PUBLIC_DIR));
// Full catalog
app.get('/api/products', (req, res) => {
res.json(PRODUCTS);
});
// Persist the selected id list
app.post('/api/select', (req, res) => {
const ids = req.body && req.body.ids;
if (!Array.isArray(ids)) {
return res.status(400).json({ error: 'body must be {ids:[...]}' });
}
const saved = writeSelections(ids);
res.json({ ok: true, count: saved.length });
});
// Read selections back
app.get('/api/selections', (req, res) => {
res.json({ ids: readSelections() });
});
// Export selected rows as CSV download
app.get('/api/export', (req, res) => {
const selected = new Set(readSelections().map((v) => String(v)));
const cols = ['dw_sku', 'mfr_sku', 'title', 'pattern_name', 'color_name', 'price'];
const rows = PRODUCTS.filter((p) => selected.has(String(p.id)));
const lines = [cols.join(',')];
for (const r of rows) {
lines.push(cols.map((c) => csvCell(r[c])).join(','));
}
const csv = lines.join('\n');
const stamp = new Date().toISOString().slice(0, 10);
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader(
'Content-Disposition',
`attachment; filename="astek-selection-${stamp}.csv"`
);
res.send(csv);
});
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', products: PRODUCTS.length, selected: readSelections().length });
});
// ---- Port scan then listen ----
function checkPort(port) {
return new Promise((resolve) => {
const tester = net
.createServer()
.once('error', () => resolve(false))
.once('listening', () => tester.close(() => resolve(true)))
.listen(port, '127.0.0.1');
});
}
async function findPort() {
for (let p = PREFERRED_PORT; p <= PORT_RANGE_END; p++) {
if (await checkPort(p)) return p;
}
throw new Error(`No free port in ${PREFERRED_PORT}..${PORT_RANGE_END}`);
}
findPort()
.then((port) => {
app.listen(port, () => {
if (port !== PREFERRED_PORT) {
console.log(`NOTE: preferred port ${PREFERRED_PORT} taken; using ${port}`);
}
console.log(`astek-import-viewer listening on http://localhost:${port}`);
});
})
.catch((err) => {
console.error('FATAL:', err.message);
process.exit(1);
});