Files
pak-jabit/catalogue/fix_prices.py
T
juxtapoandClaude Opus 5 a58a61b98c Pak Jabit: full stack — storefront, Spree customisations, catalogue tooling
A South African men's apparel store on Spree 5.6 + Next.js 15. 160 products
live, three markets, live Malaysian courier rates, customer-owned carts.

What is here:
  storefront/  the Next.js app
  backend/     ONLY the custom Spree code. Spree itself is a prebuilt image
               that mounts no app code, so every file here needs an explicit
               volume entry in docker-compose.yml or it is silently ignored.
  catalogue/   the scripts that loaded and maintain the products
  docs/        manifest, mock registry, napkins

Real .env files are committed deliberately. This lives on the house's private
Gitea, and a clone that runs beats a clone that needs a secrets handover.
LANDMINE.md maps every key, what it opens, and what must be rotated before this
repo goes anywhere less private.

The admin API key used to be a literal in six catalogue scripts. They now read
SPREE_ADMIN_KEY from the environment and refuse to run without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 22:09:55 +08:00

58 lines
1.8 KiB
Python

import json, time, requests
from pathlib import Path
import os as _os
def _spree_admin_key() -> str:
"""The Spree admin API key. Required — never defaulted."""
key = _os.environ.get("SPREE_ADMIN_KEY", "").strip()
if not key:
raise SystemExit(
"SPREE_ADMIN_KEY is not set.\n"
" export SPREE_ADMIN_KEY=<the admin API key>\n"
"It is deliberately not stored in this file — see README.md."
)
return key
API = "https://api.jux-lab.com/api/v3/admin"
H = {"X-Spree-Api-Key": _spree_admin_key(), "Content-Type": "application/json"}
PRODUCTS = Path("/root/Opus/Projects/PeakyBuildr/scraped-catalogue/products")
MYR_TO_USD = 4.8
prices = {}
for folder in PRODUCTS.iterdir():
info = folder / "info.json"
if info.exists():
prices[folder.name] = round(float(json.loads(info.read_text())["price_myr"]) * MYR_TO_USD, 2)
items, page = [], 1
while True:
d = requests.get(f"{API}/products?per_page=100&page={page}", headers=H, timeout=60).json()
items += d.get("data", [])
if page >= d.get("meta", {}).get("pages", 1):
break
page += 1
print(f"{len(items)} products, {len(prices)} source prices")
ok = fail = 0
for i, p in enumerate(items):
amt = prices.get(p["slug"])
if amt is None:
print(f" no source price: {p['slug'][:45]}")
fail += 1
continue
r = requests.patch(f"{API}/products/{p['id']}", headers=H,
json={"prices": [{"amount": amt, "currency": "USD"}]}, timeout=60).json()
if "error" in r:
fail += 1
print(f" FAIL {p['slug'][:40]}: {r['error'].get('message')}")
else:
ok += 1
if (i + 1) % 40 == 0:
print(f" {i+1}/{len(items)}")
time.sleep(0.12)
print(f"Done ok={ok} fail={fail}")