← back to Dw Collection Banner Audit

land_renders.py

73 lines

#!/usr/bin/env python3
"""
Upload the 29 room-render PNGs to Shopify + set each as its collection banner.
Uses REST image.attachment (base64) so Shopify hosts the image (no staged-upload).
Auto-detects custom vs smart collection (tries custom, falls back to smart).
Records old->new in rollback.jsonl. Detects storage-cap failures and stops.
"""
import json, os, base64, time, urllib.request, glob

HERE = os.path.dirname(os.path.abspath(__file__))
STORE = "designer-laboratory-sandbox.myshopify.com"; API = "2024-10"
TOKEN = os.environ["SHOPIFY_ADMIN_TOKEN"]
cols = {c["handle"]: c for c in json.load(open(os.path.join(HERE, "dw_collections.json")))}
prop = {r["handle"]: r for r in json.load(open(os.path.join(HERE, "proposal.json")))}

def rest_put(kind, cid, payload):
    url = f"https://{STORE}/admin/api/{API}/{kind}/{cid}.json"
    req = urllib.request.Request(url, data=json.dumps(payload).encode(),
        headers={"X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json"}, method="PUT")
    return urllib.request.urlopen(req, timeout=60)

# handles already landed as a room-render (skip to avoid re-upload)
landed = set()
rbp = os.path.join(HERE, "rollback.jsonl")
if os.path.exists(rbp):
    for line in open(rbp):
        try:
            d = json.loads(line)
            if d.get("ok") and d.get("room_render"): landed.add(d["handle"])
        except: pass

log = open(rbp, "a")
files = [f for f in sorted(glob.glob(os.path.join(HERE, "room_renders", "*.png")))
         if os.path.basename(f)[:-4] not in landed]
print(f"already-landed room-renders skipped: {len(landed)}; new to land: {len(files)}")
ok = fail = 0
for i, f in enumerate(files, 1):
    handle = os.path.basename(f)[:-4]
    c = cols.get(handle)
    if not c:
        print(f"  SKIP {handle} (no collection)"); continue
    cid = c["id"]; title = c["title"]
    b64 = base64.b64encode(open(f, "rb").read()).decode()
    old_src = (prop.get(handle) or {}).get("current_src")
    done = False
    for kind, key in [("custom_collections", "custom_collection"), ("smart_collections", "smart_collection")]:
        try:
            resp = rest_put(kind, cid, {key: {"id": cid, "image": {"attachment": b64, "alt": title}}})
            data = json.load(resp)
            img = data.get(key, {}).get("image")
            new_src = img.get("src") if img else None
            print(f"  OK   {handle}  ({kind.split('_')[0]}) -> {new_src.rsplit('/',1)[-1][:40] if new_src else '?'}")
            log.write(json.dumps({"handle": handle, "ok": True, "old_src": old_src,
                                  "new_src": new_src, "room_render": True}) + "\n")
            ok += 1; done = True; break
        except urllib.error.HTTPError as e:
            body = e.read().decode()[:200]
            if e.code == 404:
                continue  # wrong collection type, try the other
            if "STORAGE" in body.upper() or "storage" in body.lower():
                print(f"  STORAGE-CAP HIT on {handle}: {body}"); log.close()
                print("STOPPING — Shopify file storage cap reached."); raise SystemExit(2)
            print(f"  FAIL {handle}: HTTP {e.code} {body}")
            log.write(json.dumps({"handle": handle, "ok": False, "err": f"{e.code} {body}"}) + "\n")
            fail += 1; done = True; break
        except Exception as e:
            print(f"  ERR  {handle}: {str(e)[:120]}"); fail += 1; done = True; break
    if not done:
        print(f"  FAIL {handle}: neither custom nor smart matched"); fail += 1
    time.sleep(0.7)
log.close()
print(f"\nlanded ok={ok} fail={fail}")