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>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
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}")
|
||||
Reference in New Issue
Block a user