[object Object]

← back to Astek Landing

vendor-requests: self-bootstrap vendor_requests table (fix 'relation does not exist' on fresh DB)

051d76d0c6def5cae575d969f4dc5e3092246a44 · 2026-07-07 07:22:25 -0700 · Steve

Files touched

Diff

commit 051d76d0c6def5cae575d969f4dc5e3092246a44
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jul 7 07:22:25 2026 -0700

    vendor-requests: self-bootstrap vendor_requests table (fix 'relation does not exist' on fresh DB)
---
 lib/vendor-requests.js | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/lib/vendor-requests.js b/lib/vendor-requests.js
index 872fdd7..d3d70ae 100644
--- a/lib/vendor-requests.js
+++ b/lib/vendor-requests.js
@@ -38,6 +38,41 @@ function db() {
   return pool;
 }
 
+// Self-bootstrap the table so a fresh DB (Kamatera, mac2, a new sister site)
+// never throws `relation "vendor_requests" does not exist` on the first click.
+// Idempotent (IF NOT EXISTS) and memoized — the DDL runs at most once per process.
+let schemaReady = null;
+function ensureSchema() {
+  if (!schemaReady) {
+    schemaReady = db().query(`
+      CREATE TABLE IF NOT EXISTS vendor_requests (
+        id                bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+        req_no            text,
+        req_type          text,
+        vendor_code       text,
+        vendor_name       text,
+        vendor_email      text,
+        account_number    text,
+        dw_sku            text,
+        mfr_sku           text,
+        product_title     text,
+        qty               text,
+        note              text,
+        requested_by      text,
+        status            text,
+        email_subject     text,
+        email_body        text,
+        email_message_id  text,
+        created_at        timestamptz NOT NULL DEFAULT now(),
+        sent_at           timestamptz
+      );
+      CREATE INDEX IF NOT EXISTS vendor_requests_vendor_code_id_idx
+        ON vendor_requests (vendor_code, id DESC);
+    `).catch(e => { schemaReady = null; console.error('[vendor-requests] ensureSchema failed:', e.message); throw e; });
+  }
+  return schemaReady;
+}
+
 const esc = s => String(s == null ? '' : s).replace(/[&<>]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
 
 // ---- email composition — polite, account-number-only, client-anonymous ----
@@ -93,6 +128,9 @@ function reqNoFor(id, createdAt) {
 }
 
 function mountVendorRequests(app, { vendorCode, getVendor, dataDir }) {
+  // warm the schema on mount so the table exists before the first request lands
+  ensureSchema().catch(() => {});
+
   // exact email preview — same composer the send path uses; nothing persisted
   app.post('/api/request/preview', (req, res) => {
     const { type, dw_sku, mfr_sku, title, qty, note } = req.body || {};
@@ -108,6 +146,7 @@ function mountVendorRequests(app, { vendorCode, getVendor, dataDir }) {
     if (!dw_sku && !mfr_sku) return res.status(400).json({ ok: false, error: 'sku required' });
     const vendor = getVendor() || {};
     try {
+      await ensureSchema(); // guarantees the table on a cold process (no more "relation does not exist")
       const ins = await db().query(
         `INSERT INTO vendor_requests (req_type, vendor_code, vendor_name, vendor_email, account_number,
            dw_sku, mfr_sku, product_title, qty, note, requested_by, status)
@@ -156,6 +195,7 @@ function mountVendorRequests(app, { vendorCode, getVendor, dataDir }) {
 
   app.get('/api/requests', async (_req, res) => {
     try {
+      await ensureSchema();
       const { rows } = await db().query(
         `SELECT req_no, req_type, status, dw_sku, mfr_sku, product_title, qty, created_at, sent_at
          FROM vendor_requests WHERE vendor_code=$1 ORDER BY id DESC LIMIT 50`, [vendorCode]);

← 18d430d chore: macstudio3 migration — reconcile from mac2 + repoint  ·  back to Astek Landing  ·  vendor-requests: resolve george send-token/auth across Mac + 22e8ce8 →