← back to Fineartamerica Price Sync

lib/shopify.js

66 lines

// Thin Shopify Admin REST client for the LIVE Designer Wallcoverings store.
// NOTE: designer-laboratory-sandbox.myshopify.com is the real, customer-facing
// production store despite the "sandbox" handle. Every variant price write here
// is customer-facing money — callers must gate writes (see sync.js --apply).

const fs = require('fs');

const SECRETS = '/Users/macstudio3/Projects/secrets-manager/.env';
const STORE = 'designer-laboratory-sandbox.myshopify.com';
const API = '2024-10';
const VENDOR = 'Steve Abrams Studios';

function env(key) {
  const m = fs.readFileSync(SECRETS, 'utf8').match(new RegExp('^' + key + '=(.*)$', 'm'));
  return m ? m[1].trim().replace(/^["']|["']$/g, '') : null;
}
const TOKEN = env('SHOPIFY_ADMIN_TOKEN');

async function shopify(path, opts = {}) {
  const res = await fetch(`https://${STORE}/admin/api/${API}/${path}`, {
    ...opts,
    headers: {
      'X-Shopify-Access-Token': TOKEN,
      'Content-Type': 'application/json',
      ...(opts.headers || {}),
    },
  });
  const text = await res.text();
  if (!res.ok) throw new Error(`Shopify ${res.status} ${path}: ${text.slice(0, 300)}`);
  return { json: text ? JSON.parse(text) : null, headers: res.headers };
}

async function getProduct(id) {
  const { json } = await shopify(`products/${id}.json`);
  return json.product;
}

// All FAA-fulfilled products = vendor "Steve Abrams Studios" whose variants
// carry the FAA SKU signature. Paginates the vendor set.
async function listFaaProducts() {
  const out = [];
  let url = `products.json?vendor=${encodeURIComponent(VENDOR)}&limit=250`;
  while (url) {
    const { json, headers } = await shopify(url);
    for (const p of json.products) {
      const isFaa = (p.variants || []).some(v => v.sku && /artworkid\[/.test(v.sku) && /productid\[/.test(v.sku));
      if (isFaa) out.push(p);
    }
    const link = headers.get('link') || '';
    const next = link.match(/<[^>]*[?&]([^>]*page_info=[^>&]+)[^>]*>;\s*rel="next"/);
    url = next ? `products.json?limit=250&${next[1]}` : null;
  }
  return out;
}

// Gated write. Returns the updated variant.
async function updateVariantPrice(variantId, price) {
  const { json } = await shopify(`variants/${variantId}.json`, {
    method: 'PUT',
    body: JSON.stringify({ variant: { id: variantId, price: String(price) } }),
  });
  return json.variant;
}

module.exports = { shopify, getProduct, listFaaProducts, updateVariantPrice, STORE, VENDOR };