← back to Flock Fix Viewer
server.js
111 lines
#!/usr/bin/env node
// Flock Fix Viewer — live board showing each flock SKU's fields going broken -> fixed
// Reads LIVE Shopify (not the unreliable mirror). Basic auth admin/DW2024!.
const http = require('http');
const https = require('https');
const fs = require('fs');
const path = require('path');
const SHOP = 'designer-laboratory-sandbox.myshopify.com';
const TOKEN = fs.readFileSync(path.join(__dirname, '.token'), 'utf8').trim();
const AUTH = 'Basic ' + Buffer.from('admin:DW2024!').toString('base64');
// The flock set we are fixing (Phillipe Romano flocked velvet line)
const QUERY = "vendor:'Phillipe Romano' status:active (tag:'Flock Velvet' OR handle:flock OR title:flock)";
function gql(query, variables) {
return new Promise((resolve, reject) => {
const body = JSON.stringify({ query, variables: variables || {} });
const req = https.request({
host: SHOP, path: '/admin/api/2024-10/graphql.json', method: 'POST',
headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
}, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch (e) { reject(e); } }); });
req.on('error', reject); req.write(body); req.end();
});
}
const PRODUCT_Q = `query($c:String){
products(first:100, query:${JSON.stringify(QUERY)}, after:$c){
pageInfo{hasNextPage endCursor}
edges{node{
id handle title status createdAt
featuredImage{url}
tags
variants(first:8){edges{node{sku title price}}}
mMin: metafield(namespace:"global", key:"v_prods_quantity_order_min"){value}
mWidth: metafield(namespace:"global", key:"width"){value}
mUnit: metafield(namespace:"global", key:"unit_of_measure"){value}
mSqSR: metafield(namespace:"global", key:"sqft_single_roll"){value}
mSqDR: metafield(namespace:"global", key:"sqft_double_roll"){value}
mRepeat: metafield(namespace:"custom", key:"pattern_repeat"){value}
mPrint: metafield(namespace:"custom", key:"print_type"){value}
}}
}
}`;
let cache = { at: 0, rows: [] };
async function fetchStatus() {
if (Date.now() - cache.at < 4000) return cache.rows; // light throttle
let cur = null, all = [];
do {
const r = await gql(PRODUCT_Q, { c: cur });
const p = r && r.data && r.data.products;
if (!p) break;
all = all.concat(p.edges);
cur = p.pageInfo.hasNextPage ? p.pageInfo.endCursor : null;
} while (cur);
const rows = all.map(e => {
const n = e.node;
const tags = n.tags || [];
const hasQuotes = tags.some(t => String(t).replace(/["{}]/g, '').trim().toLowerCase() === 'quotes');
const vs = (n.variants.edges || []).map(v => v.node);
const roll = vs.filter(v => !/sample/i.test(v.title || ''));
const rollPrice = roll.reduce((m, v) => Math.max(m, parseFloat(v.price) || 0), 0);
const sku = (roll[0] && roll[0].sku) || (vs[0] && vs[0].sku) || '?';
const handle = n.handle;
const isDup = handle.startsWith('copy-of-') || /-\d+$/.test(handle);
const hasPrice = rollPrice > 10;
const buyable = !hasQuotes && hasPrice && n.status === 'ACTIVE';
const numericId = (n.id || '').split('/').pop();
const adminUrl = 'https://admin.shopify.com/store/designer-laboratory-sandbox/products/' + numericId;
return {
sku, handle, title: n.title, status: n.status, price: rollPrice,
img: (n.featuredImage && n.featuredImage.url) || null,
created: n.createdAt,
adminUrl,
f_quotes: !hasQuotes, // fixed when quotes gone
f_price: hasPrice, // has a real roll price
f_badge: !hasQuotes, // wrong 54"/yard badge gone when quotes gone
f_buyable: buyable,
f_min: !!(n.mMin && n.mMin.value),
f_width: !!(n.mWidth && n.mWidth.value),
f_sqft: !!(n.mSqSR && n.mSqSR.value && n.mSqDR && n.mSqDR.value),
f_specs: !!(n.mRepeat && n.mRepeat.value && n.mPrint && n.mPrint.value),
sqftSR: (n.mSqSR && n.mSqSR.value) || null,
sqftDR: (n.mSqDR && n.mSqDR.value) || null,
colorwayDup: isDup,
fixed: !hasQuotes && hasPrice, // primary fix complete
};
});
cache = { at: Date.now(), rows };
return rows;
}
const PAGE = fs.readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8');
const server = http.createServer(async (req, res) => {
if ((req.headers.authorization || '') !== AUTH) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="flock"' }); return res.end('auth required');
}
if (req.url.startsWith('/api/status')) {
try { const rows = await fetchStatus(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(rows)); }
catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: String(e) })); }
return;
}
res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(PAGE);
});
server.listen(process.env.PORT || 61438, '127.0.0.1', () => {
const port = server.address().port;
fs.writeFileSync(path.join(__dirname, '.port'), String(port));
console.log('FLOCK-FIX-VIEWER listening on http://127.0.0.1:' + port + ' (admin / DW2024!)');
});