/** * B2B admin scan (D19 step 2) — hits every read endpoint the wholesale side of * the admin panel uses on a normal working day, looking for things that are * OUTRIGHT BROKEN (500s, crashes, empty required shapes). * * Scope per the owner: wholesale customers, sales experts, products and * variants, wholesale orders, documents. Retail/storefront surfaces * (single*, wallet, comments) are D13 — preserved, not scanned. * * This is a breakage scan, not a parity audit: a 200 with plausible data * passes. Anything non-2xx is reported with its body so it can be triaged. * * Usage: node scripts/scan-admin-b2b.cjs [apiBaseUrl] */ const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); const API = (process.argv[2] || 'http://localhost:3000/api').replace(/\/$/, ''); const broken = []; const ok = []; let H = {}; async function probe(label, method, path, body) { let res; try { res = await fetch(`${API}/${path}`, { method, headers: H, body: body === undefined ? undefined : JSON.stringify(body), }); } catch (e) { broken.push({ label, method, path, status: 'NETWORK', detail: String(e) }); console.log(` ERR ${label} (${String(e)})`); return null; } let payload = null; const text = await res.text(); try { payload = JSON.parse(text); } catch { /* non-JSON */ } if (res.ok) { ok.push(label); const n = Array.isArray(payload) ? payload.length : Array.isArray(payload?.rows) ? payload.rows.length : null; console.log(` ok ${label}${n === null ? '' : ` (${n} rows)`}`); } else { const detail = (payload && (payload.message || payload.error)) || text.slice(0, 200); broken.push({ label, method, path, status: res.status, detail }); console.log(` FAIL ${label} -> ${res.status} ${JSON.stringify(detail).slice(0, 160)}`); } return payload; } const page = { page: 1, limit: 5 }; (async () => { const login = await fetch(`${API}/admin/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone: '09128081392', password: '09128081392' }), }); const auth = await login.json(); if (!auth.api_token) { console.log('login failed'); process.exit(1); } H = { 'Content-Type': 'application/json', Authorization: 'Bearer ' + auth.api_token }; console.log('\n── WHOLESALE CUSTOMERS ──────────────────────────────'); const omde = await probe('customers: omde list', 'POST', 'admin/customers-omde/list', page); await probe('customers: banker list', 'POST', 'admin/customers-omde/banker/list', page); await probe('customers: shopkeeper list', 'POST', 'admin/customers-omde/shopkeeper/list', page); await probe('customers: non-verified list', 'POST', 'admin/customers-omde/non-verified/list', page); await probe('customers: archived list', 'POST', 'admin/customers-omde/archived/list', page); await probe('customers: referrer dropdown', 'GET', 'admin/customers-omde/query/reagent'); const aCustomer = omde?.rows?.[0]; if (aCustomer) { await probe('customer: detail', 'GET', `admin/customers-omde/${aCustomer.id}`); await probe('customer: dashboard', 'GET', `admin/customers-omde/dashboard/${aCustomer.id}`); await probe('customer: orders tab', 'GET', `admin/customers-omde/${aCustomer.id}/orders`); await probe('customer: documents tab', 'GET', `admin/customers-omde/${aCustomer.id}/documents`); } console.log('\n── SALES EXPERTS ────────────────────────────────────'); const reagents = await probe('experts: list', 'POST', 'admin/customers-omde/reagent/list', page); const anExpert = reagents?.rows?.[0]; if (anExpert) { await probe('expert: detail', 'GET', `admin/customers-omde/reagent/${anExpert.id}`); await probe('expert: as admin user', 'GET', `admin/users/${anExpert.id}`); } console.log('\n── PRODUCTS & VARIANTS ──────────────────────────────'); const prods = await probe('products: list', 'POST', 'admin/products/list', page); await probe('products: variants list', 'POST', 'admin/products/variants/list', page); await probe('products: brands', 'GET', 'admin/products/brands'); await probe('products: brands table', 'POST', 'admin/products/brands-table', page); await probe('products: collections', 'GET', 'admin/products/collections'); await probe('products: show-type options', 'GET', 'admin/products/show-type-options'); const aProduct = prods?.rows?.[0]; if (aProduct) { await probe('product: detail (edit form)', 'GET', `admin/products/${aProduct.id}`); await probe('product: its variants', 'GET', `admin/products/${aProduct.id}/variants`); // The order builder posts `products_id` (stores/cart.ts:102), not `ids`. const byIds = await probe('product: by-ids', 'POST', 'admin/products/by-ids', { products_id: [aProduct.id], }); if (byIds && Array.isArray(byIds) && byIds.length === 0) { broken.push({ label: 'product: by-ids returned nothing for a known id', method: 'POST', path: 'admin/products/by-ids', status: '200-empty', detail: `asked for ${aProduct.id}, got []`, }); console.log(' FAIL product: by-ids returned [] for a product that exists'); } } console.log('\n── WHOLESALE ORDERS ─────────────────────────────────'); const wOrders = await probe('orders: wholesale list', 'POST', 'admin/orders/wholesale/list', page); await probe('orders: tamin list', 'POST', 'admin/orders/tamin/list', page); await probe('orders: status options', 'GET', 'admin/orders/status-options'); await probe('orders: type options', 'GET', 'admin/orders/type-options'); const anOrder = wOrders?.rows?.[0]; if (anOrder) { await probe('order: detail', 'GET', `admin/orders/${anOrder.id}`); } console.log('\n── DOCUMENTS ────────────────────────────────────────'); const docs = await probe('documents: list', 'POST', 'admin/documents/list', page); const aDoc = docs?.rows?.[0]; if (aDoc) await probe('document: detail', 'GET', `admin/documents/${aDoc.id}`); console.log('\n── SUPPORTING LOOKUPS THE B2B FORMS NEED ────────────'); await probe('cities: list', 'POST', 'admin/cities/list', { page: 1, limit: 500 }); await probe('branches: list', 'POST', 'admin/branches/list', page); await probe('workshops: list', 'POST', 'admin/workshops/list', page); await probe('pre-orders: list', 'POST', 'admin/pre-orders/list', page); await probe('users: list', 'POST', 'admin/users/list', page); console.log(`\n${'='.repeat(62)}`); console.log(`B2B SCAN: ${ok.length} ok, ${broken.length} broken`); if (broken.length) { console.log('\nBROKEN:'); for (const b of broken) { console.log(` - [${b.status}] ${b.label}`); console.log(` ${b.method} ${b.path}`); console.log(` ${JSON.stringify(b.detail).slice(0, 300)}`); } } console.log('='.repeat(62)); await prisma.$disconnect(); process.exit(broken.length ? 1 : 0); })().catch(async (e) => { console.error(e); await prisma.$disconnect(); process.exit(1); });