← 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})`));