← back to Quadrille Internal
vendor-requests: self-bootstrap vendor_requests table (fix 'relation does not exist' on fresh DB)
18bd667a1fd084fd944716a09fcc82c93264df50 · 2026-07-07 07:22:25 -0700 · Steve
Files touched
Diff
commit 18bd667a1fd084fd944716a09fcc82c93264df50
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 => ({ '&': '&', '<': '<', '>': '>' }[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]);
← 0a49b7e Retrofit index to internal-line-viewer spec (astek reference
·
back to Quadrille Internal
·
vendor-requests: resolve george send-token/auth across Mac + 4e73b56 →