← back to Abrams Discovery Viewer

server.js

221 lines

// Abrams v. Pabo discovery response viewer.
// Left pane: PDF (FIN or RPD for Steven or Natalia). Right pane: question rows
// with Steven + Natalia response fields. Auto-saves to data/responses.json.
// Export buttons produce CSV per side or combined.
const express = require('express');
const helmet = require('helmet');
const fs = require('fs');
const path = require('path');

const ROOT = __dirname;
const DATA_FILE = path.join(ROOT, 'data', 'responses.json');
const PORT = parseInt(process.env.PORT, 10) || 9870;

const app = express();

// Security headers via helmet (added 2026-05-04 overnight YOLO loop)
app.use(helmet({ contentSecurityPolicy: false }));
// ----- Basic Auth -----
// LAN-exposed but PII-bearing — every request must present credentials.
// Override via env or the .auth-config.json file (gitignored).
const AUTH_FILE = path.join(ROOT, '.auth-config.json');
let AUTH_USER = process.env.AUTH_USER || 'steve';
let AUTH_PASS = process.env.AUTH_PASS || '';
if (!AUTH_PASS && fs.existsSync(AUTH_FILE)) {
  try {
    const c = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf-8'));
    AUTH_USER = c.user || AUTH_USER;
    AUTH_PASS = c.pass || AUTH_PASS;
  } catch(e) {}
}
if (!AUTH_PASS) {
  // First-run bootstrap — generate a random pass and write it to .auth-config.json
  AUTH_PASS = require('crypto').randomBytes(8).toString('base64url');
  fs.writeFileSync(AUTH_FILE, JSON.stringify({ user: AUTH_USER, pass: AUTH_PASS }, null, 2), 'utf-8');
  console.log(`[auth] Generated credentials → ${AUTH_FILE}`);
  console.log(`[auth]   user: ${AUTH_USER}`);
  console.log(`[auth]   pass: ${AUTH_PASS}`);
}
app.use((req, res, next) => {
  const hdr = req.headers.authorization || '';
  if (hdr.startsWith('Basic ')) {
    const decoded = Buffer.from(hdr.slice(6), 'base64').toString('utf-8');
    const idx = decoded.indexOf(':');
    if (idx >= 0) {
      const u = decoded.slice(0, idx);
      const p = decoded.slice(idx + 1);
      if (u === AUTH_USER && p === AUTH_PASS) return next();
    }
  }
  res.set('WWW-Authenticate', 'Basic realm="Abrams v. Pabo Discovery", charset="UTF-8"');
  res.status(401).send('Authentication required.');
});

app.use(express.json({ limit: '5mb' }));

// Refuse to serve snapshot/backup files (*.bak, *.bak.*, *.pre-*) from any static
// root. Defense-in-depth so a stray editor backup or pre-deploy snapshot can never
// leak case PII via a guessable URL. (2026-05-19, fleet-refactor-sweep)
app.use((req, res, next) => {
  if (/\.(bak|pre)(\.|$)|\.pre-/i.test(req.path)) {
    return res.status(404).type('text').send('Not found.');
  }
  next();
});

app.use(express.static(path.join(ROOT, 'public')));
app.use('/pdfs', express.static(path.join(ROOT, 'pdfs')));

// ----- Question bank (FIN sections relevant to PI motor vehicle + RPD 1-17) -----
const FIN_QUESTIONS = [
  ['1.1', 'Identity of Persons Answering: Name, ADDRESS, telephone, and relationship of each PERSON who prepared or assisted in preparing these responses.'],
  ['2.1', 'State (a) your name; (b) every name you have used in the past; (c) the dates you used each name.'],
  ['2.2', 'State the date and place of your birth.'],
  ['2.3', "At the time of the INCIDENT, did you have a driver's license? If so: (a) issuing entity; (b) license number and type; (c) date of issuance; (d) all restrictions."],
  ['2.4', 'At the time of the INCIDENT, did you have any other permit/license to operate a motor vehicle? Same subparts as 2.3.'],
  ['2.5', 'State (a) present residence ADDRESS; (b) residence ADDRESSES for the past five years; (c) the dates at each.'],
  ['2.6', 'Employment: (a) present employer/self-employment name/ADDRESS/phone; (b) every employer or self-employment name/ADDRESS/dates/title/work for five years before the INCIDENT until today.'],
  ['2.7', 'Education: (a) each school from high school onward; (b) dates attended; (c) highest grade completed; (d) degrees received.'],
  ['2.8', 'Have you ever been convicted of a felony? If so, for each: (a) city/state; (b) date; (c) offense; (d) court and case number.'],
  ['2.9', 'Can you speak English with ease? If not, what language and dialect do you normally use?'],
  ['2.10', 'Can you read and write English with ease? If not, what language and dialect do you normally use?'],
  ['2.11', 'At the time of the INCIDENT were you acting as agent/employee for any PERSON? If so: (a) name/ADDRESS/phone; (b) duties.'],
  ['2.12', 'Did you or any other person have any physical, emotional, or mental disability/condition that may have contributed to the INCIDENT? If so: (a) name/ADDRESS/phone; (b) condition; (c) how it contributed.'],
  ['2.13', 'Within 24 hours before the INCIDENT did you or any person involved use alcohol, marijuana, or any drug/medication (Rx or not)? For each: (a)–(g) per the form.'],
  ['4.1', 'Was any insurance policy in effect under which you were/might be insured for damages from the INCIDENT? For each policy: (a) coverage; (b) insurer name/ADDRESS; (c) named insured name/ADDRESS/phone; (d) policy number; (e) limits; (f) reservation/coverage dispute; (g) custodian.'],
  ['4.2', 'Are you self-insured under any statute for damages arising from the INCIDENT? If so, specify the statute.'],
  ['6.1', 'Do you attribute any physical, mental, or emotional injuries to the INCIDENT? (If "no," skip 6.2–6.7.)'],
  ['6.2', 'Identify each injury you attribute to the INCIDENT and the area of your body affected.'],
  ['6.3', 'Do you still have any complaints attributed to the INCIDENT? For each: (a) description; (b) subsiding/same/worsening; (c) frequency and duration.'],
  ['6.4', 'Did you receive any consultation/examination/treatment from a HEALTH CARE PROVIDER (excluding experts)? For each: (a) name/ADDRESS/phone; (b) type of service; (c) dates; (d) charges to date.'],
  ['6.5', 'Have you taken any medication (Rx or not) due to injuries attributed to the INCIDENT? For each: (a) name; (b) prescriber; (c) date prescribed/furnished; (d) start/stop dates; (e) cost to date.'],
  ['6.6', 'Any other medical services necessitated by the injuries (e.g., ambulance, nursing, prosthetics)? For each: (a) nature; (b) date; (c) cost; (d) provider name/ADDRESS/phone.'],
  ['6.7', 'Has any HEALTH CARE PROVIDER advised future or additional treatment? For each injury: (a) provider name/ADDRESS; (b) complaints; (c) nature, duration, estimated cost.'],
  ['7.1', 'Do you attribute any vehicle/property damage to the INCIDENT? For each item: (a) describe; (b) damage nature/location; (c) amount claimed and how calculated; (d) if sold: seller, date, price.'],
  ['7.2', 'Has a written estimate or evaluation been made for any 7.1 property? For each: (a) preparer/date; (b) custodian; (c) amount.'],
  ['7.3', 'Has any 7.1 property been repaired? For each: (a) date; (b) description; (c) cost; (d) repairer; (e) who paid.'],
  ['8.1', 'Do you attribute any loss of income or earning capacity to the INCIDENT? (If "no," skip 8.2–8.8.)'],
  ['8.2', 'State (a) nature of your work; (b) job title at time of INCIDENT; (c) date employment began.'],
  ['8.3', 'State the last date before the INCIDENT that you worked for compensation.'],
  ['8.4', 'State your monthly income at the time of the INCIDENT and how it was calculated.'],
  ['8.5', 'State the date you returned to work at each place of employment following the INCIDENT.'],
  ['8.6', 'State the dates you did not work and lost income as a result of the INCIDENT.'],
  ['8.7', 'State the total income you have lost to date as a result of the INCIDENT and how it was calculated.'],
  ['8.8', 'Will you lose income in the future as a result of the INCIDENT? If so: (a) facts; (b) estimate; (c) time unable to work; (d) calculation method.'],
  ['9.1', 'Any other damages attributed to the INCIDENT? For each: (a) nature; (b) date; (c) amount; (d) name/ADDRESS/phone of obligee.'],
  ['9.2', 'Do any DOCUMENTS support 9.1? Describe each and identify custodian.'],
  ['10.1', 'Before the INCIDENT, did you have complaints/injuries to the same body parts now claimed? For each: (a) description; (b) start/end dates; (c) provider name/ADDRESS/phone.'],
  ['10.2', 'List all physical, mental, and emotional disabilities you had immediately before the INCIDENT.'],
  ['10.3', 'After the INCIDENT, did you sustain injuries of the kind now claimed? For each: (a) date/place; (b) other persons; (c) nature; (d) provider; (e) treatment nature/duration.'],
  ['11.1', 'Except this action, in the past 10 years have you filed an action or made a written claim for personal injury compensation? For each: (a)–(f) per the form.'],
  ['11.2', "In the past 10 years have you made a written claim/demand for workers' comp benefits? For each: (a)–(g) per the form."],
  ['12.1', 'Name/ADDRESS/phone of each individual: (a) who witnessed the INCIDENT or events immediately before/after; (b) made statements at the scene; (c) heard statements at the scene; (d) anyone with knowledge.'],
  ['12.2', 'Have you or anyone acting on your behalf interviewed any individual about the INCIDENT? For each: (a) interviewee; (b) date; (c) interviewer.'],
  ['12.3', 'Any written/recorded statement obtained from any individual about the INCIDENT? For each: (a) source; (b) obtainer; (c) date; (d) custodian.'],
  ['12.4', 'Any photographs, films, or videotapes depicting place/object/individual concerning the INCIDENT or plaintiff\'s injuries? (a) number; (b) subjects; (c) dates; (d) photographer; (e) custodian.'],
  ['12.5', 'Any diagram, reproduction, or model of any place/thing concerning the INCIDENT? For each: (a) type; (b) subject; (c) custodian.'],
  ['12.6', 'Was a report made by any PERSON concerning the INCIDENT? (a) author; (b) date/type; (c) for whom; (d) custodian.'],
  ['12.7', 'Have you or anyone acting on your behalf inspected the scene? For each: (a) inspector; (b) date.'],
  ['13.1', 'Have you or anyone acting on your behalf conducted surveillance of any party? For each: (a)–(d) per the form.'],
  ['13.2', 'Has a written report been prepared on the surveillance? For each: (a) title; (b) date; (c) author; (d) custodian.'],
  ['14.1', 'Do you contend any PERSON involved violated any statute/ordinance/regulation that was a legal cause of the INCIDENT? Identify name/ADDRESS/phone and the law violated.'],
  ['14.2', 'Was any PERSON cited or charged with a violation as a result of the INCIDENT? For each: (a)–(d) per the form.'],
  ['15.1', 'Identify each denial/affirmative defense in your pleadings; for each: (a) facts; (b) persons with knowledge; (c) DOCUMENTS and custodians.'],
  ['17.1', 'Is each response to the request for admissions an unqualified admission? If not, for each non-admission: (a)–(d) per the form.'],
  ['20.1', 'State the date, time, and place of the INCIDENT (closest street ADDRESS or intersection).'],
  ['20.2', 'For each vehicle involved: (a) year/make/model/license; (b) driver; (c) other occupants; (d) registered owner; (e) lessee; (f) other owner/lien holder; (g) who gave permission to drive.'],
  ['20.3', 'ADDRESS/location where your trip began and destination.'],
  ['20.4', 'Describe your route and the location of each non-routine stop.'],
  ['20.5', 'Street, lane, and direction of travel of each vehicle for the 500 feet before the INCIDENT.'],
  ['20.6', 'Did the INCIDENT occur at an intersection? If so, describe all traffic control devices/signals/signs.'],
  ['20.7', 'Was a traffic signal facing you at the time? (a)–(d) per the form.'],
  ['20.8', 'How the INCIDENT occurred: speed/direction/location of each vehicle (a) just before, (b) at the time, (c) just after.'],
  ['20.9', 'Information that a malfunction/defect in a vehicle caused the INCIDENT? (a)–(d) per the form.'],
  ['20.10', 'Information that a malfunction/defect contributed to the injuries? (a)–(d) per the form.'],
  ['20.11', 'Name/ADDRESS/phone of each owner/possessor of each vehicle since the INCIDENT.'],
];

const RPD_QUESTIONS = [
  [1, 'All DOCUMENTS that set forth or establish all HEALTH CARE obtained by YOU as a result of the SUBJECT INCIDENT.'],
  [2, 'All DOCUMENTS that set forth or establish all HEALTH CARE COSTS incurred by YOU (charged, paid, accepted as payment in full).'],
  [3, 'All DOCUMENTS that set forth or establish YOUR past, present and future loss of earnings or income claimed.'],
  [4, 'All written or electronically recorded statements by any person who saw/heard events immediately prior to, during or after the SUBJECT INCIDENT.'],
  [5, 'All PHOTOGRAPHS/FILMS/TAPES depicting bodily injuries sustained by YOU.'],
  [6, 'All PHOTOGRAPHS/FILMS/TAPES depicting damage to any vehicle.'],
  [7, 'All PHOTOGRAPHS/FILMS/TAPES depicting the SUBJECT INCIDENT.'],
  [8, 'All DOCUMENTS YOU obtained from any police agency regarding the SUBJECT INCIDENT.'],
  [9, 'All EXPLANATIONS OF BENEFITS for HEALTH CARE COSTS (Howell v. Hamilton Meats evaluation).'],
  [10, 'All correspondence between YOU and any defendants regarding the SUBJECT INCIDENT.'],
  [11, 'All diaries or journal entries mentioning the SUBJECT INCIDENT or YOUR claimed injuries.'],
  [12, 'All DOCUMENTS YOU prepared, or contributed to preparing, that reference the SUBJECT INCIDENT.'],
  [13, 'All DOCUMENTS that identify any limitation on YOUR ability to perform work as a result of the SUBJECT INCIDENT.'],
  [14, 'All DOCUMENTS identified in YOUR response to Form Interrogatories, Set One.'],
  [15, 'All PHOTOGRAPHS/FILMS/TAPES depicting the scene of the SUBJECT INCIDENT.'],
  [16, 'A copy of YOUR health insurance card.'],
  [17, 'A copy of YOUR Medicare card.'],
];

function buildBank() {
  // Each row gets a stable id keyed by source + plaintiff + item.
  const rows = [];
  for (const plaintiff of ['Steven Marcus Abrams', 'Natalia Debra Dultz']) {
    const pCode = plaintiff.startsWith('Steven') ? 'abrams' : 'dultz';
    for (const [num, q] of FIN_QUESTIONS) {
      rows.push({ id: `${pCode}-fin-${num}`, plaintiff, source: 'FIN', item: num, question: q, pdf: `${pCode}-fin.pdf` });
    }
    for (const [num, q] of RPD_QUESTIONS) {
      rows.push({ id: `${pCode}-rpd-${num}`, plaintiff, source: 'RPD', item: String(num), question: q, pdf: `${pCode}-rpd.pdf` });
    }
  }
  return rows;
}
const BANK = buildBank();

function loadResponses() {
  try { return JSON.parse(fs.readFileSync(DATA_FILE, 'utf-8')); } catch(e) { return {}; }
}
function saveResponses(data) {
  fs.mkdirSync(path.dirname(DATA_FILE), { recursive: true });
  fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2), 'utf-8');
}

app.get('/api/bank', (req, res) => res.json(BANK));
app.get('/api/responses', (req, res) => res.json(loadResponses()));

app.post('/api/responses', (req, res) => {
  const { id, response, status, documents, notes } = req.body || {};
  if (!id) return res.status(400).json({ error: 'missing id' });
  const data = loadResponses();
  data[id] = {
    ...(data[id] || {}),
    ...(response !== undefined ? { response } : {}),
    ...(status !== undefined ? { status } : {}),
    ...(documents !== undefined ? { documents } : {}),
    ...(notes !== undefined ? { notes } : {}),
    updated: new Date().toISOString(),
  };
  saveResponses(data);
  res.json({ ok: true, row: data[id] });
});

app.get('/api/csv', (req, res) => {
  const data = loadResponses();
  const esc = s => /[",\n]/.test(String(s||'')) ? '"' + String(s).replace(/"/g,'""') + '"' : String(s||'');
  const lines = [['Plaintiff','Source','Set','Item','Question','Response','Documents','Status','Notes','Updated'].join(',')];
  for (const r of BANK) {
    const v = data[r.id] || {};
    lines.push([r.plaintiff, r.source, 'One', r.item, r.question, v.response || '', v.documents || '', v.status || '', v.notes || '', v.updated || ''].map(esc).join(','));
  }
  res.set('Content-Type', 'text/csv; charset=utf-8');
  res.set('Content-Disposition', 'attachment; filename="abrams-pabo-discovery.csv"');
  res.send(lines.join('\n'));
});

// Bind via env (default 0.0.0.0 for LAN access). Note: this server holds
// discovery responses (PII, legal case data) — anyone on your WiFi can reach
// it. If that ever feels wrong, set BIND=127.0.0.1.
const BIND = process.env.BIND || '0.0.0.0';
app.listen(PORT, BIND, () => {
  console.log(`Abrams discovery viewer on http://${BIND}:${PORT}`);
});