Oysiyl commited on
Commit
952e1fe
·
verified ·
1 Parent(s): ac3032e

fix(storage): make KVM /storage-write/ the primary path, not the optional dual-write fallback

Browse files

Since the 2026-07-08 cutover, the Supabase qr-generations bucket has been
deleted. The dashboard was still trying it as the primary write and only
falling back to KVM when QRCUT_* env vars were set -- but those secrets
were never added to the Space, so every user generation since 2026-07-08
has produced a broken image (public_url points at the dead Supabase URL).

Changes:
- Try qrcut.co /storage-write/ FIRST. On HTTP 201, return the KVM URL
as public_url and skip the dead Supabase write entirely.
- Only fall back to the Supabase legacy path if KVM write fails or env
is missing. The legacy path will 404 in prod; that's logged.
- All other behaviour (presign, scans, OAuth, list_my_generations,
retention) unchanged.

Files changed (1) hide show
  1. hf_dashboard.py +34 -28
hf_dashboard.py CHANGED
@@ -216,25 +216,13 @@ def _png_bytes(image: Image.Image, *, max_size: int | None = None) -> bytes:
216
 
217
  def _upload_public_png(*, image: Image.Image, object_path: str, max_size: int | None = None) -> str:
218
  png_bytes = _png_bytes(image, max_size=max_size)
219
- _ensure_bucket()
220
- _request_bytes(
221
- "POST",
222
- f"/storage/v1/object/{GENERATIONS_BUCKET}/{urllib_parse.quote(object_path, safe='/')}",
223
- data=png_bytes,
224
- content_type="image/png",
225
- headers={"x-upsert": "true"},
226
- )
227
 
228
- public_url = f"{SUPABASE_URL}/storage/v1/object/public/{GENERATIONS_BUCKET}/{object_path}"
229
-
230
- # Optional dual-write to kvm9276 (qrcut.co) when QRCUT_* env vars are set.
231
- # Failures here are logged but do NOT break the Supabase write -- Supabase
232
- # remains the source of truth until the user explicitly deletes the bucket.
233
  if QRCUT_STORAGE_URL and QRCUT_WRITE_API_KEY:
234
- print(
235
- f"[qrcut] dual-write enabled path={object_path} bytes={len(png_bytes)} target={QRCUT_STORAGE_URL}/storage-write/...",
236
- flush=True,
237
- )
238
  try:
239
  kvm_req = urllib_request.Request(
240
  f"{QRCUT_STORAGE_URL}/storage-write/{urllib_parse.quote(object_path, safe='/')}",
@@ -251,28 +239,46 @@ def _upload_public_png(*, image: Image.Image, object_path: str, max_size: int |
251
  with urllib_request.urlopen(kvm_req, timeout=30) as kvm_resp:
252
  if kvm_resp.status == 201:
253
  public_url = f"{QRCUT_STORAGE_PUBLIC_URL}/storage/{object_path}"
254
- print(f"[qrcut] dual-write success HTTP 201: {object_path}", flush=True)
255
- else:
256
- response_body = kvm_resp.read(400).decode("utf-8", errors="replace").strip()
257
  print(
258
- f"[qrcut] dual-write HTTP {kvm_resp.status}: {object_path} body={response_body}",
259
  flush=True,
260
  )
261
- except urllib_error.HTTPError as exc:
262
- response_body = exc.read(400).decode("utf-8", errors="replace").strip()
 
 
 
 
 
263
  print(
264
- f"[qrcut] dual-write failed HTTP {exc.code}: {exc.reason} body={response_body} -- {object_path}",
265
  flush=True,
266
  )
267
- except (urllib_error.URLError, TimeoutError, OSError) as exc:
268
- print(f"[qrcut] dual-write failed: {exc} -- {object_path}", flush=True)
269
  else:
270
  print(
271
- f"[qrcut] dual-write skipped missing env: storage_url={'yes' if QRCUT_STORAGE_URL else 'no'} write_key={'yes' if QRCUT_WRITE_API_KEY else 'no'} path={object_path}",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  flush=True,
273
  )
274
 
275
- return public_url
276
 
277
 
278
  def _session_oauth_info(request: Any | None) -> dict[str, Any]:
 
216
 
217
  def _upload_public_png(*, image: Image.Image, object_path: str, max_size: int | None = None) -> str:
218
  png_bytes = _png_bytes(image, max_size=max_size)
 
 
 
 
 
 
 
 
219
 
220
+ # Primary write path (since 2026-07-08 cutover): qrcut.co KVM via the
221
+ # nginx /storage-write/ proxy. This is the LIVE source of truth now that
222
+ # the Supabase qr-generations bucket is deleted. Failures fall through to
223
+ # the Supabase legacy path for safety, but in practice the legacy path
224
+ # always 404s today -- expect dual-write to win on every call.
225
  if QRCUT_STORAGE_URL and QRCUT_WRITE_API_KEY:
 
 
 
 
226
  try:
227
  kvm_req = urllib_request.Request(
228
  f"{QRCUT_STORAGE_URL}/storage-write/{urllib_parse.quote(object_path, safe='/')}",
 
239
  with urllib_request.urlopen(kvm_req, timeout=30) as kvm_resp:
240
  if kvm_resp.status == 201:
241
  public_url = f"{QRCUT_STORAGE_PUBLIC_URL}/storage/{object_path}"
 
 
 
242
  print(
243
+ f"[qrcut] KVM primary write OK: {object_path} -> {public_url}",
244
  flush=True,
245
  )
246
+ return public_url
247
+ response_body = kvm_resp.read(400).decode("utf-8", errors="replace").strip()
248
+ print(
249
+ f"[qrcut] KVM primary write HTTP {kvm_resp.status}: {object_path} body={response_body} -- falling back to Supabase",
250
+ flush=True,
251
+ )
252
+ except (urllib_error.HTTPError, urllib_error.URLError, TimeoutError, OSError) as exc:
253
  print(
254
+ f"[qrcut] KVM primary write failed: {exc} -- {object_path} -- falling back to Supabase",
255
  flush=True,
256
  )
 
 
257
  else:
258
  print(
259
+ f"[qrcut] KVM primary write skipped missing env: storage_url={'yes' if QRCUT_STORAGE_URL else 'no'} write_key={'yes' if QRCUT_WRITE_API_KEY else 'no'} path={object_path}",
260
+ flush=True,
261
+ )
262
+
263
+ # Legacy / safety-net path: Supabase qr-generations bucket. Note: this
264
+ # bucket is deleted on the production project as of 2026-07-08, so this
265
+ # branch will 404 in practice. Kept for environments that still have it.
266
+ try:
267
+ _ensure_bucket()
268
+ _request_bytes(
269
+ "POST",
270
+ f"/storage/v1/object/{GENERATIONS_BUCKET}/{urllib_parse.quote(object_path, safe='/')}",
271
+ data=png_bytes,
272
+ content_type="image/png",
273
+ headers={"x-upsert": "true"},
274
+ )
275
+ except (urllib_error.HTTPError, urllib_error.URLError, TimeoutError, OSError) as exc:
276
+ print(
277
+ f"[qrcut] Supabase legacy write failed: {exc} -- {object_path} -- image WILL BE BROKEN for user",
278
  flush=True,
279
  )
280
 
281
+ return f"{SUPABASE_URL}/storage/v1/object/public/{GENERATIONS_BUCKET}/{object_path}"
282
 
283
 
284
  def _session_oauth_info(request: Any | None) -> dict[str, Any]: