WFDASHDRIVER-APP/scripts/check-pb-schema.mjs
SJCoetz 85c63b336c feat: add PO/Notes popovers, supplier color sidebar, fix popover clipping
- Add ItemDetailModal showing PO document (view/download/share/email) + buyer notes
- Add FileText (PO) and MessageSquare (notes) buttons to compact item list with popovers
- Fix photo upload: append to order_items.order_item_attachements (PB file field)
- Add colored left sidebar stripe per supplier group for visual distinction
- Fix popover z-index so they appear above following cards (overflow-visible + z-50)
- Extend PurchaseOrder type with documents, email_payload, supplier
- Add order_item_attachements and expand.purchase_order to OrderItem
- onShowDetails prop wired through SupplierGroup and AreaGroup

Co-authored-by: Orca <help@stably.ai>
2026-08-02 21:38:38 +02:00

117 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
// scripts/check-pb-schema.mjs
//
// Pre-build sanity check: probes every PocketBase filter used by the driver
// app against the live server and reports any reference to an unknown
// field. Catches the exact "added a clause to a filter, forgot to add the
// field to PB" class of bug that produced the July-2026 4×400 incident.
//
// Usage:
// npm run check-pb-schema
//
// Env (optional — defaults match this project's dev PB + e2e creds):
// NEXT_PUBLIC_POCKETBASE_URL=https://your-pb.example.com
// PB_USER_EMAIL=...
// PB_USER_PASSWORD=...
//
// Exit codes:
// 0 all filters and identifiers parse against the live schema
// 1 one or more filters reference unknown fields
// 2 couldn't authenticate against PB (fix env / network first)
import PocketBase from 'pocketbase';
// --- config ---------------------------------------------------------
const PB = process.env.NEXT_PUBLIC_POCKETBASE_URL
|| 'https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io';
const EMAIL = process.env.PB_USER_EMAIL || 'david.els1@outlook.com';
const PASSWORD = process.env.PB_USER_PASSWORD || '1234567890';
// One row per `filter:` string in `lib/pocketbase.ts`. Literal values are
// placeholders — we don't want to MATCH data, we want the filter to PARSE.
// PB validates identifier names at the parser level and 400s the moment
// it hits one it doesn't know, regardless of the value.
const FILTERS = [
{ collection: 'order_items', filter: 'drv_id = "placeholder" && item_status = "assigned"' },
{ collection: 'order_items', filter: 'item_status = "collection_pool"' },
{ collection: 'order_items', filter: 'problem_flag = true || item_status = "problem"' },
{ collection: 'driver_locations', filter: 'relation = "placeholder" && timestamp >= "2026-01-01T00:00:00Z"' },
];
// --- helpers (declared up here so they're in scope for the loop) ----
function truncate(s, n) {
return s.length > n ? s.slice(0, n - 1) + '…' : s;
}
// PB operator tokens we want to skip when picking identifiers out of a
// filter string. Anything not in here AND not in a quoted string AND
// matching `/^[a-zA-Z_]…/` is treated as a candidate field name.
const OPS = new Set(['&&', '||', '!=', '=', '<', '>', '<=', '>=']);
function extractIdentifiers(filter) {
const out = new Set();
// Strip quoted strings so their contents aren't picked up as idents.
const stripped = filter.replace(/"[^"]*"/g, '');
for (const tok of stripped.split(/[\s()]+/)) {
if (!tok) continue;
if (OPS.has(tok)) continue;
if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(tok)) continue;
// Reduce `expand.path` to just `expand` for the bisect probe —
// the relation FIELD is what PB validates, not the remote column.
out.add(tok.split('.')[0]);
}
return [...out];
}
// --- run ------------------------------------------------------------
const pb = new PocketBase(PB);
pb.autoCancellation(false);
console.log(`[check-pb-schema] target: ${PB}`);
try {
await pb.collection('users').authWithPassword(EMAIL, PASSWORD);
} catch (e) {
console.error(`[check-pb-schema] FATAL: cannot authenticate — ${e.message}`);
console.error(` check NEXT_PUBLIC_POCKETBASE_URL / PB_USER_EMAIL / PB_USER_PASSWORD`);
process.exit(2);
}
console.log(`[check-pb-schema] auth: OK (as ${pb.authStore.record?.id ?? EMAIL})\n`);
let failed = 0;
// Pass 1 — probe each filter at face value. Most filters are happy
// here and the loop is fast.
console.log('Pass 1 — probe every filter at face value:');
for (const { collection, filter } of FILTERS) {
try {
await pb.collection(collection).getList(1, 1, { filter });
console.log(` [OK] ${collection.padEnd(24)} ${truncate(filter, 70)}`);
} catch (e) {
failed++;
console.log(` [FAIL] ${collection.padEnd(24)} ${truncate(filter, 70)}`);
console.log(` status=${e.status ?? '??'} msg="${e.message}"`);
// Pass 2 — bisect: probe each top-level identifier in the filter
// as its own one-clause filter. Whatever 400s is an unknown field.
// Unknown fields 400 regardless of the operator/value used.
const suspects = extractIdentifiers(filter);
for (const id of suspects) {
try {
await pb.collection(collection).getList(1, 1, { filter: `${id} = ""` });
console.log(` · ok : ${id}`);
} catch (probeErr) {
console.log(` ⚠ unknown : ${id} (PB: ${probeErr.message})`);
}
}
}
}
console.log('');
if (failed > 0) {
console.error(`[check-pb-schema] ${failed} filter(s) reference unknown fields.`);
console.error(`[check-pb-schema] Either add the field to PB, or remove the clause from the filter.`);
console.error(`[check-pb-schema] See lib/pocketbase.ts header for verified-live schema notes.`);
process.exit(1);
}
console.log(`[check-pb-schema] all filters valid.`);