← back to Designerwallcoverings
scripts/backfill-clarke-redirects.py
59 lines
#!/usr/bin/env python3
"""
Backfill 301 redirects for the renamed Clarke handles: old hashed handle -> new
dwkk-{number} handle. Reads every old->new pair from rename-clarke-handles-ledger.jsonl
and creates a Shopify URL redirect (/products/<old> -> /products/<new>) so the old
hashed URLs stop 404-ing. Idempotent (skips pairs that already have a redirect).
REQUIRES a token with write_online_store_navigation (the default one lacks it —
this will ACCESS_DENIED until the scope is added). $0 (Shopify Admin GraphQL).
Env: DRYRUN=1 to only report the count.
"""
import os,sys,json,urllib.request,time
SHOP="designer-laboratory-sandbox.myshopify.com"
DRYRUN=os.environ.get("DRYRUN")=="1"
HERE=os.path.dirname(os.path.abspath(__file__))
LEDGER=os.path.join(HERE,"rename-clarke-handles-ledger.jsonl")
TOKEN_VAR=os.environ.get("TOKEN_VAR","SHOPIFY_ADMIN_TOKEN") # Steve 2026-06-18: use SHOPIFY_DW_ADMIN_TOKEN_CONTENT for redirects
def _token():
for p in (os.path.join(HERE,"..",".env"), os.path.expanduser("~/Projects/secrets-manager/.env")):
try:
for ln in open(p):
if ln.startswith(TOKEN_VAR+"="): return ln.split("=",1)[1].strip()
except FileNotFoundError: pass
sys.exit(f"no {TOKEN_VAR}")
TOKEN=_token(); URL=f"https://{SHOP}/admin/api/2024-10/graphql.json"
def gql(q,v=None):
req=urllib.request.Request(URL,data=json.dumps({"query":q,"variables":v or {}}).encode(),
headers={"X-Shopify-Access-Token":TOKEN,"Content-Type":"application/json"})
return json.load(urllib.request.urlopen(req,timeout=40))
# unique old->new pairs from the ledger (last write wins; only successful renames)
pairs={}
for ln in open(LEDGER):
try: r=json.loads(ln)
except: continue
old,new=r.get("old"),r.get("new")
if old and new and old!=new and old.startswith("dwkk-g"):
pairs[old]=new
print(f"old->new redirect pairs: {len(pairs)}")
if DRYRUN:
for o,n in list(pairs.items())[:5]: print(f" /products/{o} -> /products/{n}")
sys.exit(0)
RC='mutation($r:UrlRedirectInput!){ urlRedirectCreate(urlRedirect:$r){ urlRedirect{id} userErrors{field message} } }'
made=skip=err=0
for i,(old,new) in enumerate(pairs.items(),1):
try:
r=gql(RC,{"r":{"path":f"/products/{old}","target":f"/products/{new}"}}).get("data",{}).get("urlRedirectCreate") or {}
ue=r.get("userErrors") or []
if any("already" in (e.get("message","").lower()) for e in ue): skip+=1
elif ue: err+=1; (print(" err:",ue[:1]) if err<=3 else None)
elif r.get("urlRedirect"): made+=1
except Exception as e:
err+=1
if err<=3: print(" exc:",str(e)[:100])
if i%200==0: print(f" [{i}/{len(pairs)}] made={made} skip={skip} err={err}",flush=True)
time.sleep(0.3)
print(f"DONE redirects made={made} already-existed={skip} errors={err} of {len(pairs)}")