← back to Designer Wallcoverings

pending-approval/refresh-product-image.sh

58 lines

#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# Reusable: replace a Shopify product's image(s) with a local hi-res file.
# GATED PROD WRITE — default = DRY RUN. Destructive (deletes images) only with CONFIRM=1.
#
# Usage:
#   PID=<product_id> IMG=<file> ALT="<alt text>" DEL="id1 id2 ..." \
#     [CONFIRM=1] bash refresh-product-image.sh
#
# Adds IMG (base64 attachment, position 1, given ALT), then deletes each id in DEL.
# Idempotent on add: skips if an image with IMG's basename is already attached.
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail

SHOP="designer-laboratory-sandbox.myshopify.com"; API="2024-10"
: "${PID:?set PID}"; : "${IMG:?set IMG}"; : "${ALT:?set ALT}"; DEL="${DEL:-}"
[ -f "$IMG" ] || { echo "ERROR: image file not found: $IMG"; exit 1; }

TOKEN="${SHOPIFY_ADMIN_TOKEN:-}"
[ -z "$TOKEN" ] && TOKEN="$(grep -hE '^SHOPIFY_ADMIN_TOKEN=' "$HOME/Projects/secrets-manager/.env" 2>/dev/null | head -1 | cut -d= -f2-)"
[ -z "$TOKEN" ] && { echo "ERROR: no SHOPIFY_ADMIN_TOKEN"; exit 1; }

BASE="https://$SHOP/admin/api/$API"; H=(-H "X-Shopify-Access-Token: $TOKEN"); HJ=(-H "Content-Type: application/json")
DRY=1; [ "${CONFIRM:-0}" = "1" ] && DRY=0
BN="$(basename "$IMG")"
say(){ printf '\n\033[1m%s\033[0m\n' "$*"; }

DIMS="$(sips -g pixelWidth -g pixelHeight "$IMG" 2>/dev/null | awk '/pixelWidth/{w=$2}/pixelHeight/{h=$2}END{print w"x"h}')"
say "Product $PID on $SHOP | image: $BN ($DIMS) | mode: $([ $DRY = 1 ] && echo DRY-RUN || echo LIVE)"

say "0) Current images"
curl -s "${H[@]}" "$BASE/products/$PID.json" \
 | python3 -c 'import json,sys;p=json.load(sys.stdin)["product"];print("  title:",p["title"]);[print("   ",i["id"],i["width"],"x",i["height"],i["src"].split("/")[-1]) for i in p["images"]]'

say "1) Add hi-res image (alt: $ALT)"
ALREADY="$(curl -s "${H[@]}" "$BASE/products/$PID.json" | python3 -c "import json,sys;print('yes' if any('${BN%.*}' in (i.get('src') or '') for i in json.load(sys.stdin)['product']['images']) else 'no')")"
if [ "$ALREADY" = yes ]; then echo "  already attached — skip add";
elif [ $DRY = 1 ]; then echo "  [dry-run] would POST base64 attachment ($(wc -c <"$IMG") bytes) at position 1";
else
  PAY=$(mktemp /tmp/rp-img.XXXXXX)
  python3 - "$IMG" "$ALT" "$BN" > "$PAY" <<'PY'
import base64,json,sys
b64=base64.b64encode(open(sys.argv[1],'rb').read()).decode()
print(json.dumps({"image":{"attachment":b64,"filename":sys.argv[3],"position":1,"alt":sys.argv[2]}}))
PY
  R=$(curl -s -w $'\n%{http_code}' -X POST "$BASE/products/$PID/images.json" "${H[@]}" "${HJ[@]}" --data-binary @"$PAY")
  C="${R##*$'\n'}"; B="${R%$'\n'*}"; rm -f "$PAY"; echo "  POST HTTP $C"
  case "$C" in 200|201) echo "$B" | python3 -c 'import json,sys;i=json.load(sys.stdin)["image"];print("  added",i["id"],i["width"],"x",i["height"])';; *) echo "  ERR: $B"; exit 1;; esac
fi

say "2) Delete listed images: ${DEL:-(none)}"
for IID in $DEL; do
  if [ $DRY = 1 ]; then echo "  [dry-run] would DELETE image $IID";
  else curl -s -X DELETE "$BASE/products/$PID/images/$IID.json" "${H[@]}" -o /dev/null -w "  delete $IID HTTP %{http_code}\n"; fi
done

say "3) $([ $DRY = 1 ] && echo '(dry-run — re-run with CONFIRM=1 to apply)' || { echo 'Verify'; curl -s "${H[@]}" "$BASE/products/$PID.json" | python3 -c 'import json,sys;p=json.load(sys.stdin)["product"];print("  images:",[(i["id"],i["width"],"x",i["height"]) for i in p["images"]])'; })"