admin: view order PDFs from MinIO (signed forms, prepared filings, evidence)

Adds a Documents section to the compliance-order detail drawer so you can
review the actual filing PDFs before approving an order:
  GET /api/v1/admin/compliance-orders/:id/documents  list viewable objects
  GET /api/v1/admin/compliance-orders/:id/document?key=&token=  stream one

Key discovery pulls from esign_records (unsigned + signed docs per order),
intake_data.filing_status (pdf_minio_path, attested_pdf, evidence/*), and the
order's engagement_letter / rmd_packet columns.

Rather than hand out presigned URLs (MinIO's public host is IP-allowlisted to a
few office IPs, so links break elsewhere), the API streams the object through
itself from internal minio:9000, gated by the admin JWT. The stream endpoint
accepts the token via ?token= (new middleware requireAdminQueryOrHeader) so a
PDF opens in a new tab, and refuses any key that isn't one of the order's own
documents.
This commit is contained in:
justin 2026-06-16 00:20:15 -05:00
parent d65f5ea279
commit bce5db4a09
3 changed files with 192 additions and 1 deletions

View file

@ -363,13 +363,40 @@
(order.fulfillment_status === "ready_to_file" ? `<button id="drawer-approve" class="btn btn-blue wfull" style="margin-top:16px;">Approve &amp; file this order</button>` : "") +
((order.payment_status === "paid" && !order.intake_data_validated) ? `<button id="drawer-rearm" class="btn btn-amber wfull" style="margin-top:8px;">Re-arm intake reminder</button>` : "") +
`<div class="section-h">Intake data</div><pre>${esc(JSON.stringify(intake, null, 2))}</pre>` +
`<div class="section-h">Documents</div><div id="drawer-docs"><div class="muted" style="font-size:12px;">Loading documents…</div></div>` +
`<div class="section-h">Audit log</div>${auditHtml}`;
const da = $("drawer-approve");
if (da) da.addEventListener("click", () => approveOrder(order.order_number, order.service_name || order.service_slug));
const dr = $("drawer-rearm");
if (dr) dr.addEventListener("click", () => rearmIntake(order.order_number));
loadDocuments(order.order_number);
} catch (e) { $("drawer-body").innerHTML = `<div style="color:#b91c1c;">Failed to load: ${esc(e.message)}</div>`; }
}
async function loadDocuments(orderNumber) {
const box = $("drawer-docs");
if (!box) return;
try {
const { documents } = await api("/api/v1/admin/compliance-orders/" + encodeURIComponent(orderNumber) + "/documents");
if (!documents || !documents.length) {
box.innerHTML = '<div class="muted" style="font-size:12px;">No documents on file yet.</div>';
return;
}
// Stream endpoint takes the JWT via ?token= so it opens in a new tab.
const tok = encodeURIComponent(token());
box.innerHTML = documents.map((d) => {
const u = API + "/api/v1/admin/compliance-orders/" + encodeURIComponent(orderNumber)
+ "/document?key=" + encodeURIComponent(d.key) + "&token=" + tok;
return `<div class="svc-row"><div class="svc-left">
<span style="font-size:13px;">${esc(d.label)}</span>
<span class="small">${esc(d.key.split("/").pop())}</span>
</div><div class="svc-right">
<a href="${u}" target="_blank" rel="noopener" class="btn btn-blue btn-sm" style="text-decoration:none;">View</a>
</div></div>`;
}).join("");
} catch (e) {
box.innerHTML = `<div style="color:#b91c1c;font-size:12px;">Could not load documents: ${esc(e.message)}</div>`;
}
}
$("drawer-close").addEventListener("click", () => $("drawer").classList.add("hidden"));
$("drawer-backdrop").addEventListener("click", () => $("drawer").classList.add("hidden"));