← back to Re Flyer Aggregator
server.js
76 lines
#!/usr/bin/env node
// TK-10708 Read-only deal-assets viewer/API (the consumable surface CRCP/RENTV read,
// per codex). Serves usre.deal_assets_v joined to recent_commercial_deals. Zero deps
// (node http + psql). Basic-auth admin/DW2024! per the DW viewer standard.
import http from 'node:http';
import { execFileSync } from 'node:child_process';
import { readFileSync, existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const ROOT = dirname(fileURLToPath(import.meta.url));
const PORT = process.env.PORT || 9860;
const USER = process.env.BASIC_USER || 'admin';
const PASS = process.env.BASIC_PASS || 'DW2024!';
const DB = process.env.USRE_DB || 'usre';
// TK-10708 (decision B): the consolidated normalized cross-source flyer feed.
// Built by scripts/build-normalized-flyers.mjs into data/flyers-normalized.json in the
// TK-10708 schema {source_build,listing_id,title,address,flyer_url,generated_at,asset_type,
// rights_basis,tier}. Served read-only; every row carries rights_basis + tier for the
// compliance tiering. This surface NEVER re-hosts a file — flyer_url only.
const NORMALIZED = join(ROOT, 'data', 'flyers-normalized.json');
function normalizedFlyers() {
if (!existsSync(NORMALIZED)) return { count: 0, flyers: [], note: 'run scripts/build-normalized-flyers.mjs first' };
return JSON.parse(readFileSync(NORMALIZED, 'utf8'));
}
const q = sql => execFileSync('psql', [DB, '-t', '-A', '-c', sql], { encoding: 'utf8' }).trim();
function dealAssets() {
// deals that HAVE at least one consumer-visible asset, newest+priciest first, assets nested
const deals = q(`SELECT row_to_json(t) FROM (
SELECT DISTINCT d.county_fips,d.doc_number,d.address,d.city,d.county_name,
d.sale_price::bigint AS sale_price,d.sale_date,d.ctype,d.sqft
FROM recent_commercial_deals d
JOIN deal_assets_v v ON v.county_fips=d.county_fips AND v.doc_number=d.doc_number
ORDER BY sale_price DESC) t`).split('\n').filter(Boolean).map(JSON.parse);
const assets = q(`SELECT row_to_json(t) FROM (
SELECT county_fips,doc_number,asset_type,tier,rights_basis,title,source_name,
source_landing_url,local_path,match_confidence
FROM deal_assets_v) t`).split('\n').filter(Boolean).map(JSON.parse);
const byKey = {};
for (const a of assets) (byKey[a.county_fips + '|' + a.doc_number] ||= []).push(a);
return deals.map(d => ({ ...d, assets: byKey[d.county_fips + '|' + d.doc_number] || [] }));
}
const server = http.createServer((req, res) => {
// basic auth gate (the 401 is the healthy gate)
const hdr = req.headers.authorization || '';
const [, b64] = hdr.split(' ');
const [u, p] = Buffer.from(b64 || '', 'base64').toString().split(':');
if (u !== USER || p !== PASS) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="re-flyers"' }); return res.end('Auth required');
}
try {
if (req.url.startsWith('/api/deal-assets')) {
const data = dealAssets();
res.writeHead(200, { 'content-type': 'application/json' });
return res.end(JSON.stringify({ count: data.length, deals: data }));
}
// TK-10708 unified normalized cross-source flyer feed (additive; deal-assets route unchanged)
if (req.url.startsWith('/api/flyers')) {
const data = normalizedFlyers();
res.writeHead(200, { 'content-type': 'application/json' });
return res.end(JSON.stringify(data));
}
if (req.url === '/health') { res.writeHead(200); return res.end('ok'); }
res.writeHead(200, { 'content-type': 'text/html' });
return res.end(readFileSync(join(ROOT, 'public', 'index.html')));
} catch (e) {
res.writeHead(500, { 'content-type': 'text/plain' }); res.end('err: ' + e.message);
}
});
server.listen(PORT, () => console.log(`re-flyers viewer on :${PORT} (basic-auth ${USER})`));