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>
49 lines
1.4 KiB
Python
49 lines
1.4 KiB
Python
import json, sys, time, requests
|
|
|
|
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"
|
|
KEY = _spree_admin_key()
|
|
H = {"X-Spree-Api-Key": KEY, "Content-Type": "application/json"}
|
|
|
|
products, page = [], 1
|
|
while True:
|
|
d = requests.get(f"{API}/products?per_page=100&page={page}", headers=H, timeout=60).json()
|
|
products += d.get("data", [])
|
|
if page >= d.get("meta", {}).get("pages", 1):
|
|
break
|
|
page += 1
|
|
|
|
print(f"{len(products)} products")
|
|
ok = fail = 0
|
|
for i, p in enumerate(products):
|
|
vid = p.get("default_variant_id")
|
|
if not vid:
|
|
fail += 1
|
|
continue
|
|
r = requests.patch(f"{API}/products/{p['id']}/variants/{vid}", headers=H,
|
|
json={"count_on_hand": 100, "track_inventory": False}, 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) % 25 == 0:
|
|
print(f" {i+1}/{len(products)}")
|
|
time.sleep(0.15)
|
|
|
|
print(f"Done. ok={ok} fail={fail}")
|