AI-QR-code-generator / hf_dashboard.py
Oysiyl's picture
fix: set qrcut upload User-Agent for Cloudflare
ac3032e
Raw
History Blame Contribute Delete
46.1 kB
from __future__ import annotations
import copy
import hashlib
import io
import json
import math
import os
import re
from collections import Counter
from datetime import datetime, timedelta, timezone
from typing import Any, Mapping
from urllib import error as urllib_error
from urllib import parse as urllib_parse
from urllib import request as urllib_request
from PIL import Image
SUPABASE_URL = os.getenv("SUPABASE_URL", "").rstrip("/")
SUPABASE_SERVICE_ROLE_KEY = os.getenv("SUPABASE_SERVICE_ROLE_KEY", "")
GENERATIONS_BUCKET = os.getenv("SUPABASE_GENERATIONS_BUCKET", "qr-generations")
# Optional: dual-write to kvm9276 (qrcut.co) when these env vars are set.
# When unset, behavior is identical to today (Supabase only).
QRCUT_STORAGE_URL = os.getenv("QRCUT_STORAGE_URL", "").rstrip("/")
QRCUT_STORAGE_PUBLIC_URL = os.getenv("QRCUT_STORAGE_PUBLIC_URL", QRCUT_STORAGE_URL).rstrip("/")
QRCUT_WRITE_API_KEY = os.getenv("QRCUT_WRITE_API_KEY", "")
class DashboardError(RuntimeError):
pass
_BUCKET_READY = False
def _require_supabase() -> None:
if not SUPABASE_URL or not SUPABASE_SERVICE_ROLE_KEY:
raise DashboardError(
"Supabase is not configured. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY."
)
def _quote_filter(value: str) -> str:
return urllib_parse.quote(str(value), safe="")
def _headers(*, json_body: bool = True, extra: Mapping[str, str] | None = None) -> dict[str, str]:
headers = {
"apikey": SUPABASE_SERVICE_ROLE_KEY,
"Authorization": f"Bearer {SUPABASE_SERVICE_ROLE_KEY}",
"User-Agent": "hermes-space-server/1.0",
}
if json_body:
headers["Content-Type"] = "application/json"
if extra:
headers.update(dict(extra))
return headers
def _request_json(
method: str,
path: str,
*,
payload: Any | None = None,
headers: Mapping[str, str] | None = None,
timeout: float = 20,
) -> Any:
_require_supabase()
data = None
body_headers = _headers(json_body=True, extra=headers)
if payload is not None:
data = json.dumps(payload).encode("utf-8")
req = urllib_request.Request(
f"{SUPABASE_URL}{path}",
data=data,
headers=body_headers,
method=method,
)
try:
with urllib_request.urlopen(req, timeout=timeout) as response:
raw = response.read()
if not raw:
return None
return json.loads(raw.decode("utf-8"))
except urllib_error.HTTPError as exc:
detail = exc.read().decode("utf-8", "ignore")
raise DashboardError(f"Supabase request failed ({exc.code} {method} {path}): {detail}") from exc
except urllib_error.URLError as exc:
raise DashboardError(f"Supabase request failed ({method} {path}): {exc}") from exc
def _request_bytes(
method: str,
path: str,
*,
data: bytes,
content_type: str,
headers: Mapping[str, str] | None = None,
timeout: float = 30,
) -> None:
_require_supabase()
body_headers = _headers(
json_body=False,
extra={"Content-Type": content_type, **(dict(headers or {}))},
)
req = urllib_request.Request(
f"{SUPABASE_URL}{path}",
data=data,
headers=body_headers,
method=method,
)
try:
with urllib_request.urlopen(req, timeout=timeout):
return None
except urllib_error.HTTPError as exc:
detail = exc.read().decode("utf-8", "ignore")
raise DashboardError(f"Supabase upload failed ({exc.code} {method} {path}): {detail}") from exc
except urllib_error.URLError as exc:
raise DashboardError(f"Supabase upload failed ({method} {path}): {exc}") from exc
def _table_select(table: str, *, query: str) -> list[dict[str, Any]]:
result = _request_json("GET", f"/rest/v1/{table}?{query}")
if isinstance(result, list):
return result
return []
def _parse_timestamp(value: Any) -> datetime | None:
text = str(value or "").strip()
if not text:
return None
if text.endswith("Z"):
text = text[:-1] + "+00:00"
if "." in text:
main, suffix = text.split(".", 1)
tz_index_plus = suffix.find("+")
tz_index_minus = suffix.find("-")
tz_index_candidates = [idx for idx in (tz_index_plus, tz_index_minus) if idx != -1]
tz_index = min(tz_index_candidates) if tz_index_candidates else -1
if tz_index != -1:
fraction = suffix[:tz_index]
timezone_part = suffix[tz_index:]
else:
fraction = suffix
timezone_part = ""
if fraction:
fraction = (fraction + "000000")[:6]
text = f"{main}.{fraction}{timezone_part}"
if len(text) >= 3 and text[-3] in {"+", "-"} and text[-2:].isdigit():
text = text + ":00"
try:
return datetime.fromisoformat(text)
except ValueError:
return None
def _insert_row(table: str, row: Mapping[str, Any], *, upsert: bool = False) -> list[dict[str, Any]]:
prefer = "return=representation"
if upsert:
prefer += ",resolution=merge-duplicates"
result = _request_json(
"POST",
f"/rest/v1/{table}",
payload=row,
headers={"Prefer": prefer},
)
if isinstance(result, list):
return result
return []
def _patch_rows(table: str, *, filters: str, values: Mapping[str, Any]) -> list[dict[str, Any]]:
result = _request_json(
"PATCH",
f"/rest/v1/{table}?{filters}",
payload=values,
headers={"Prefer": "return=representation"},
)
if isinstance(result, list):
return result
return []
def _ensure_bucket() -> None:
global _BUCKET_READY
if _BUCKET_READY:
return
try:
_request_json(
"POST",
"/storage/v1/bucket",
payload={
"id": GENERATIONS_BUCKET,
"name": GENERATIONS_BUCKET,
"public": True,
},
)
except DashboardError as exc:
message = str(exc)
if "Duplicate" not in message and "already exists" not in message:
raise
_BUCKET_READY = True
def _png_bytes(image: Image.Image, *, max_size: int | None = None) -> bytes:
image = image.convert("RGB")
if max_size is not None:
image = image.copy()
image.thumbnail((max_size, max_size))
buf = io.BytesIO()
image.save(buf, format="PNG", optimize=True)
return buf.getvalue()
def _upload_public_png(*, image: Image.Image, object_path: str, max_size: int | None = None) -> str:
png_bytes = _png_bytes(image, max_size=max_size)
_ensure_bucket()
_request_bytes(
"POST",
f"/storage/v1/object/{GENERATIONS_BUCKET}/{urllib_parse.quote(object_path, safe='/')}",
data=png_bytes,
content_type="image/png",
headers={"x-upsert": "true"},
)
public_url = f"{SUPABASE_URL}/storage/v1/object/public/{GENERATIONS_BUCKET}/{object_path}"
# Optional dual-write to kvm9276 (qrcut.co) when QRCUT_* env vars are set.
# Failures here are logged but do NOT break the Supabase write -- Supabase
# remains the source of truth until the user explicitly deletes the bucket.
if QRCUT_STORAGE_URL and QRCUT_WRITE_API_KEY:
print(
f"[qrcut] dual-write enabled path={object_path} bytes={len(png_bytes)} target={QRCUT_STORAGE_URL}/storage-write/...",
flush=True,
)
try:
kvm_req = urllib_request.Request(
f"{QRCUT_STORAGE_URL}/storage-write/{urllib_parse.quote(object_path, safe='/')}",
data=png_bytes,
headers={
"X-QRCUT-WRITE-KEY": QRCUT_WRITE_API_KEY,
"Content-Type": "image/png",
"X-Upsert": "true",
"User-Agent": "AI-QR-Code-Generator/1.0 (+https://huggingface.co/spaces/Oysiyl/AI-QR-code-generator)",
"Accept": "application/json, text/plain, */*",
},
method="PUT",
)
with urllib_request.urlopen(kvm_req, timeout=30) as kvm_resp:
if kvm_resp.status == 201:
public_url = f"{QRCUT_STORAGE_PUBLIC_URL}/storage/{object_path}"
print(f"[qrcut] dual-write success HTTP 201: {object_path}", flush=True)
else:
response_body = kvm_resp.read(400).decode("utf-8", errors="replace").strip()
print(
f"[qrcut] dual-write HTTP {kvm_resp.status}: {object_path} body={response_body}",
flush=True,
)
except urllib_error.HTTPError as exc:
response_body = exc.read(400).decode("utf-8", errors="replace").strip()
print(
f"[qrcut] dual-write failed HTTP {exc.code}: {exc.reason} body={response_body} -- {object_path}",
flush=True,
)
except (urllib_error.URLError, TimeoutError, OSError) as exc:
print(f"[qrcut] dual-write failed: {exc} -- {object_path}", flush=True)
else:
print(
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}",
flush=True,
)
return public_url
def _session_oauth_info(request: Any | None) -> dict[str, Any]:
if request is None:
return {}
session = getattr(request, "session", None) or {}
oauth_info = session.get("oauth_info") if isinstance(session, Mapping) else None
if isinstance(oauth_info, Mapping):
return dict(oauth_info)
return {}
def _request_headers(request: Any | None) -> Mapping[str, Any]:
if request is None:
return {}
headers = getattr(request, "headers", None)
if isinstance(headers, Mapping):
return headers
return {}
def _build_anon_id(request: Any | None, source: str = "ui", fallback: str = "anonymous") -> str | None:
if request is None:
return None
request_session = getattr(request, "session_hash", None)
headers = _request_headers(request)
raw = "|".join(
[
source or "unknown",
str(request_session or ""),
str(headers.get("x-forwarded-for", "")),
str(headers.get("user-agent", "")),
fallback,
]
)
if raw.count("|") == 4 and all(not part for part in raw.split("|")[:-1]):
return None
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:24]
def _build_browser_fingerprint(request: Any | None, source: str = "ui") -> str | None:
if request is None:
return None
headers = _request_headers(request)
raw = "|".join(
[
source or "unknown",
str(headers.get("x-forwarded-for", "")),
str(headers.get("user-agent", "")),
str(headers.get("accept-language", "")),
]
)
if raw.count("|") == 3 and all(not part for part in raw.split("|")):
return None
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:24]
def get_current_user_context(request: Any | None, username_hint: str | None = None) -> dict[str, Any] | None:
oauth_info = _session_oauth_info(request)
raw_userinfo = oauth_info.get("userinfo")
userinfo: Mapping[str, Any] = raw_userinfo if isinstance(raw_userinfo, Mapping) else {}
hinted_username = str(username_hint or "").strip() if oauth_info else ""
username = str(userinfo.get("preferred_username") or getattr(request, "username", "") or hinted_username or "").strip()
if not username:
return None
stable_provider_subject = str(userinfo.get("sub") or "").strip()
provider_subject = stable_provider_subject or f"hf-username:{username.lower()}"
display_name = str(userinfo.get("name") or username or "").strip()
avatar_url = str(userinfo.get("picture") or "").strip()
profile_url = str(userinfo.get("profile") or (f"https://huggingface.co/{username}" if username else "")).strip()
email = str(userinfo.get("email") or "").strip() or None
return {
"provider": "huggingface",
"provider_subject": provider_subject,
"stable_provider_subject": stable_provider_subject or None,
"provisional_provider_subject": f"hf-username:{username.lower()}",
"stable_identity": bool(stable_provider_subject),
"username": username,
"display_name": display_name or username,
"avatar_url": avatar_url or None,
"profile_url": profile_url or None,
"email": email,
"raw_profile": {"userinfo": userinfo, "oauth_info": {k: v for k, v in oauth_info.items() if k != "access_token"}},
}
def ensure_app_user_for_request(request: Any | None, username_hint: str | None = None) -> dict[str, Any] | None:
user = get_current_user_context(request, username_hint=username_hint)
if user is None:
return None
exact_filters = (
f"provider=eq.{_quote_filter(user['provider'])}"
f"&provider_subject=eq.{_quote_filter(user['provider_subject'])}"
"&select=id,app_user_id,provider_subject"
"&limit=1"
)
existing = _table_select("user_identities", query=exact_filters)
existing_row = existing[0] if existing else None
provisional_row = None
if not existing_row and user.get("stable_identity"):
provisional_filters = (
f"provider=eq.{_quote_filter(user['provider'])}"
f"&provider_subject=eq.{_quote_filter(user['provisional_provider_subject'])}"
"&select=id,app_user_id,provider_subject"
"&limit=1"
)
provisional = _table_select("user_identities", query=provisional_filters)
provisional_row = provisional[0] if provisional else None
identity_row = existing_row or provisional_row
app_user_id = str(identity_row.get("app_user_id") or "").strip() if identity_row else ""
now_iso = datetime.now(timezone.utc).isoformat()
if not app_user_id:
inserted_users = _insert_row(
"app_users",
{
"display_name": user["display_name"],
"avatar_url": user["avatar_url"],
"created_at": now_iso,
"updated_at": now_iso,
"last_login_at": now_iso,
},
)
if not inserted_users:
raise DashboardError("Failed to create app user row.")
app_user_id = str(inserted_users[0]["id"])
_insert_row(
"user_identities",
{
"app_user_id": app_user_id,
"provider": user["provider"],
"provider_subject": user["provider_subject"],
"username": user["username"],
"email": user["email"],
"raw_profile": user["raw_profile"],
"created_at": now_iso,
"updated_at": now_iso,
},
upsert=True,
)
else:
_patch_rows(
"app_users",
filters=f"id=eq.{_quote_filter(app_user_id)}",
values={
"display_name": user["display_name"],
"avatar_url": user["avatar_url"],
"updated_at": now_iso,
"last_login_at": now_iso,
},
)
identity_provider_subject = user["provider_subject"]
if identity_row:
identity_provider_subject = str(identity_row.get("provider_subject") or user["provider_subject"])
identity_filters = (
f"provider=eq.{_quote_filter(user['provider'])}"
f"&provider_subject=eq.{_quote_filter(identity_provider_subject)}"
)
_patch_rows(
"user_identities",
filters=identity_filters,
values={
"provider_subject": user["provider_subject"],
"username": user["username"],
"email": user["email"],
"raw_profile": user["raw_profile"],
"updated_at": now_iso,
},
)
_claim_anonymous_generations(request, app_user_id)
return {
**user,
"app_user_id": app_user_id,
}
def _claim_anonymous_generations(request: Any | None, app_user_id: str) -> int:
if not app_user_id:
return 0
total = 0
claimed_ids: set[str] = set()
now_iso = datetime.now(timezone.utc).isoformat()
anon_id = _build_anon_id(request)
if anon_id:
rows = _patch_rows(
"generations",
filters=(
f"user_id=eq.{_quote_filter(anon_id)}"
"&app_user_id=is.null"
),
values={
"app_user_id": app_user_id,
"updated_at": now_iso,
},
)
claimed_ids.update(str(row.get("id") or "") for row in rows)
total += len(rows)
session_hash = str(getattr(request, "session_hash", "") or "").strip() if request is not None else ""
if session_hash:
rows = _patch_rows(
"generations",
filters=(
f"metadata->pending_claim->>session_hash=eq.{_quote_filter(session_hash)}"
"&app_user_id=is.null"
),
values={
"app_user_id": app_user_id,
"updated_at": now_iso,
},
)
if anon_id:
total += len([row for row in rows if str(row.get("id") or "") not in claimed_ids])
else:
total += len(rows)
claimed_ids.update(str(row.get("id") or "") for row in rows)
browser_fingerprint = _build_browser_fingerprint(request)
if browser_fingerprint:
rows = _patch_rows(
"generations",
filters=(
f"metadata->pending_claim->>browser_fingerprint=eq.{_quote_filter(browser_fingerprint)}"
"&app_user_id=is.null"
),
values={
"app_user_id": app_user_id,
"updated_at": now_iso,
},
)
total += len([row for row in rows if str(row.get("id") or "") not in claimed_ids])
return total
def persist_generation(
*,
request: Any | None,
generation_id: str,
qr_mode: str,
prompt: str,
text_input: str,
input_type: str,
status: str,
settings_dict: Mapping[str, Any],
url_normalization: Mapping[str, Any] | None,
shortener_result: Mapping[str, Any] | None,
final_image: Image.Image,
) -> str | None:
user = ensure_app_user_for_request(request)
anon_id = _build_anon_id(request)
if user is None and anon_id is None:
return None
timestamp = datetime.now(timezone.utc)
owner_id = str(user["app_user_id"]) if user else str(anon_id)
object_prefix = f"users/{owner_id}/{timestamp.strftime('%Y/%m/%d')}/{generation_id}"
completed_image_url = _upload_public_png(
image=final_image,
object_path=f"{object_prefix}.png",
)
thumbnail_url = _upload_public_png(
image=final_image,
object_path=f"{object_prefix}_thumb.png",
max_size=320,
)
normalized_destination_url = None
if input_type == "URL" and url_normalization:
normalized_destination_url = str(url_normalization.get("normalized_url") or "").strip() or None
short_link_id = None
short_code = None
short_url = None
shortener_expires_at = None
shortener_applied = False
if shortener_result:
short_link_id = str(shortener_result.get("short_link_id") or "").strip() or None
short_code = str(shortener_result.get("code") or "").strip() or None
short_url = str(shortener_result.get("short_url") or "").strip() or None
shortener_expires_at = str(shortener_result.get("expires_at") or "").strip() or None
shortener_applied = bool(shortener_result.get("applied"))
credit_cost = 4 if str(qr_mode).lower() == "artistic" else 1
metadata = {
"settings": dict(settings_dict),
}
if user:
metadata["auth"] = {
"provider": user["provider"],
"provider_subject": user["provider_subject"],
"username": user["username"],
"display_name": user["display_name"],
"profile_url": user["profile_url"],
}
elif anon_id:
metadata["pending_claim"] = {
"anon_id": anon_id,
"session_hash": str(getattr(request, "session_hash", "") or "") or None,
"browser_fingerprint": _build_browser_fingerprint(request),
}
row = {
"id": generation_id,
"user_id": owner_id,
"app_user_id": user["app_user_id"] if user else None,
"destination_url": str(text_input or ""),
"qr_mode": str(qr_mode or "standard"),
"preset_id": None,
"prompt": prompt,
"status": status,
"credit_cost": credit_cost,
"input_type": input_type,
"destination_url_normalized": normalized_destination_url,
"effective_qr_text": str(settings_dict.get("effective_qr_text") or text_input or ""),
"shortener_applied": shortener_applied,
"short_link_id": short_link_id,
"short_code": short_code,
"short_url": short_url,
"shortener_expires_at": shortener_expires_at,
"completed_image_url": completed_image_url,
"thumbnail_url": thumbnail_url,
"metadata": metadata,
"updated_at": timestamp.isoformat(),
}
_insert_row("generations", row)
if user:
return f"Saved to My QR Codes for @{user['username']}."
return "Saved this QR in the current browser session. Open My QR Codes while signed in to claim it."
def _ensure_user_for_request_compat(request: Any | None, username_hint: str | None = None) -> dict[str, Any] | None:
if username_hint:
return ensure_app_user_for_request(request, username_hint=username_hint)
return ensure_app_user_for_request(request)
def list_my_generations(request: Any | None, username_hint: str | None = None) -> dict[str, Any]:
user = _ensure_user_for_request_compat(request, username_hint=username_hint)
if user is None:
session_hash = str(getattr(request, "session_hash", "") or "").strip() if request is not None else ""
browser_fingerprint = _build_browser_fingerprint(request)
session_rows_by_id: dict[str, dict[str, Any]] = {}
def _load_session_rows(filter_expr: str) -> None:
query = (
"select=id,app_user_id,created_at,updated_at,status,input_type,qr_mode,prompt,destination_url_original,destination_url_normalized,"
"effective_qr_text,shortener_applied,short_link_id,short_code,short_url,shortener_expires_at,completed_image_url,thumbnail_url,metadata"
f"&{filter_expr}"
"&order=created_at.desc"
)
for row in _table_select("dashboard_generation_summaries", query=query):
row_id = str(row.get("id") or "")
if row_id and row_id not in session_rows_by_id:
session_rows_by_id[row_id] = row
if session_hash:
_load_session_rows(f"metadata->pending_claim->>session_hash=eq.{_quote_filter(session_hash)}")
if browser_fingerprint:
_load_session_rows(
f"metadata->pending_claim->>browser_fingerprint=eq.{_quote_filter(browser_fingerprint)}"
)
pending_rows = sorted(
session_rows_by_id.values(),
key=lambda row: str(row.get("created_at") or ""),
reverse=True,
)
pending_rows = _sync_record_destinations_from_short_links(pending_rows)
gallery: list[tuple[str, str]] = []
for row in pending_rows:
image = str(row.get("thumbnail_url") or row.get("completed_image_url") or "").strip()
created = str(row.get("created_at") or "")[:16].replace("T", " ")
label = f"{str(row.get('qr_mode') or 'qr').title()} Β· {created}"
gallery.append((image, label))
if pending_rows:
owned_count = sum(1 for row in pending_rows if row.get("app_user_id"))
pending_count = len(pending_rows) - owned_count
return {
"authenticated": False,
"status": (
f"Session view: found {len(pending_rows)} QR code(s) from this browser "
f"({owned_count} already attached to your account, {pending_count} still pending claim). "
"If Hugging Face auth looks broken, you can still browse these here and retry refresh after login."
),
"gallery": gallery,
"records": pending_rows,
"user": None,
}
return {
"authenticated": False,
"status": "Sign in with Hugging Face to see your saved QR codes and analytics.",
"gallery": [],
"records": [],
"user": None,
}
query = (
"select=id,app_user_id,created_at,updated_at,status,input_type,qr_mode,prompt,destination_url_original,destination_url_normalized,"
"effective_qr_text,shortener_applied,short_link_id,short_code,short_url,shortener_expires_at,completed_image_url,thumbnail_url,metadata"
f"&app_user_id=eq.{_quote_filter(user['app_user_id'])}"
"&order=created_at.desc"
)
rows = _table_select("dashboard_generation_summaries", query=query)
session_rows_by_id: dict[str, dict[str, Any]] = {str(row.get("id") or ""): row for row in rows if str(row.get("id") or "")}
session_hash = str(getattr(request, "session_hash", "") or "").strip() if request is not None else ""
browser_fingerprint = _build_browser_fingerprint(request)
def _merge_session_rows(filter_expr: str) -> None:
session_query = (
"select=id,app_user_id,created_at,updated_at,status,input_type,qr_mode,prompt,destination_url_original,destination_url_normalized,"
"effective_qr_text,shortener_applied,short_link_id,short_code,short_url,shortener_expires_at,completed_image_url,thumbnail_url,metadata"
f"&{filter_expr}"
"&order=created_at.desc"
)
for row in _table_select("dashboard_generation_summaries", query=session_query):
row_id = str(row.get("id") or "")
if row_id and row_id not in session_rows_by_id:
session_rows_by_id[row_id] = row
if session_hash:
_merge_session_rows(f"metadata->pending_claim->>session_hash=eq.{_quote_filter(session_hash)}")
if browser_fingerprint:
_merge_session_rows(
f"metadata->pending_claim->>browser_fingerprint=eq.{_quote_filter(browser_fingerprint)}"
)
rows = sorted(
session_rows_by_id.values(),
key=lambda row: str(row.get("created_at") or ""),
reverse=True,
)
rows = _sync_record_destinations_from_short_links(rows)
gallery: list[tuple[str, str]] = []
for row in rows:
image = str(row.get("thumbnail_url") or row.get("completed_image_url") or "").strip()
created = str(row.get("created_at") or "")[:16].replace("T", " ")
label = f"{str(row.get('qr_mode') or 'qr').title()} Β· {created}"
gallery.append((image, label))
display_name = user.get("display_name") or user.get("username")
return {
"authenticated": True,
"status": f"Signed in as {display_name} (@{user['username']}). Found {len(rows)} saved QR code(s).",
"gallery": gallery,
"records": rows,
"user": user,
}
def _extract_short_link_id(record: Mapping[str, Any]) -> str | None:
short_link_id = str(record.get("short_link_id") or "").strip()
if short_link_id:
return short_link_id
short_code = str(record.get("short_code") or "").strip()
if not short_code:
short_url = str(record.get("short_url") or "").strip()
if short_url:
short_code = short_url.rstrip("/").rsplit("/", 1)[-1]
if not short_code:
return None
rows = _table_select(
"short_links",
query=f"select=id&code=eq.{_quote_filter(short_code)}&limit=1",
)
if not rows:
return None
return str(rows[0].get("id") or "").strip() or None
def _sync_record_destinations_from_short_links(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
hydrated_rows = copy.deepcopy(rows)
short_link_ids = sorted(
{
short_link_id
for short_link_id in (_extract_short_link_id(row) for row in hydrated_rows)
if short_link_id
}
)
if not short_link_ids:
return hydrated_rows
filters = "id=in.(%s)" % ",".join(_quote_filter(short_link_id) for short_link_id in short_link_ids)
short_link_rows = _table_select(
"short_links",
query=(
"select=id,original_url,normalized_url"
f"&{filters}"
),
)
short_link_map = {
str(row.get("id") or "").strip(): row
for row in short_link_rows
if str(row.get("id") or "").strip()
}
for row in hydrated_rows:
short_link_id = _extract_short_link_id(row)
if not short_link_id:
continue
short_link = short_link_map.get(short_link_id)
if not short_link:
continue
original_url = str(short_link.get("original_url") or "").strip()
normalized_url = str(short_link.get("normalized_url") or "").strip()
if original_url:
row["destination_url_original"] = original_url
if normalized_url:
row["destination_url_normalized"] = normalized_url
return hydrated_rows
def _request_matches_record_session(request: Any | None, record: Mapping[str, Any]) -> bool:
metadata = record.get("metadata")
metadata_map: Mapping[str, Any] = metadata if isinstance(metadata, Mapping) else {}
pending_claim = metadata_map.get("pending_claim")
pending_claim_map: Mapping[str, Any] = pending_claim if isinstance(pending_claim, Mapping) else {}
request_session_hash = str(getattr(request, "session_hash", "") or "").strip() if request is not None else ""
record_session_hash = str(pending_claim_map.get("session_hash") or "").strip()
if request_session_hash and record_session_hash and request_session_hash == record_session_hash:
return True
request_browser_fingerprint = str(_build_browser_fingerprint(request) or "").strip()
record_browser_fingerprint = str(pending_claim_map.get("browser_fingerprint") or "").strip()
if request_browser_fingerprint and record_browser_fingerprint and request_browser_fingerprint == record_browser_fingerprint:
return True
return False
def _authorize_record_access(
request: Any | None,
record: Mapping[str, Any],
*,
username_hint: str | None = None,
) -> dict[str, Any] | None:
user = _ensure_user_for_request_compat(request, username_hint=username_hint)
if user is None:
if not _request_matches_record_session(request, record):
raise DashboardError("Please sign in first.")
return None
if str(record.get("app_user_id") or "") != str(user.get("app_user_id") or "") and not _request_matches_record_session(request, record):
raise DashboardError("That QR code does not belong to the current user.")
return user
_URL_TRACKING_PARAM_PREFIXES = ("utm_", "mc_", "mkt_", "pk_")
_URL_TRACKING_PARAM_NAMES = {
"fbclid",
"gclid",
"dclid",
"gbraid",
"wbraid",
"msclkid",
"igshid",
"mibextid",
"s_cid",
"si",
"vero_conv",
"vero_id",
"wickedid",
"yclid",
}
def _should_strip_tracking_param(name: str) -> bool:
lowered = str(name or "").strip().lower()
if not lowered:
return False
if lowered in _URL_TRACKING_PARAM_NAMES:
return True
return lowered.startswith(_URL_TRACKING_PARAM_PREFIXES)
def _normalize_url_for_dashboard(text_input: str) -> dict[str, Any]:
raw_input = str(text_input or "")
stripped_input = raw_input.strip()
result = {
"original_input": raw_input,
"normalized_url": stripped_input,
"normalized_qr_text": stripped_input,
"tracking_params_removed": 0,
"chars_saved": max(0, len(raw_input) - len(stripped_input)),
"changed": stripped_input != raw_input,
}
if not stripped_input:
return result
parse_target = stripped_input
if not re.match(r"^[a-zA-Z][a-zA-Z0-9+.-]*://", parse_target):
parse_target = f"https://{parse_target.lstrip('/')}"
parsed = urllib_parse.urlsplit(parse_target)
if parsed.scheme.lower() not in {"http", "https"}:
return result
hostname = (parsed.hostname or "").lower()
if not hostname:
return result
port = parsed.port
netloc = hostname
if parsed.username:
auth = parsed.username
if parsed.password:
auth = f"{auth}:{parsed.password}"
netloc = f"{auth}@{netloc}"
if port and not (
(parsed.scheme.lower() == "http" and port == 80)
or (parsed.scheme.lower() == "https" and port == 443)
):
netloc = f"{netloc}:{port}"
filtered_query: list[tuple[str, str]] = []
tracking_params_removed = 0
for key, value in urllib_parse.parse_qsl(parsed.query, keep_blank_values=True):
if _should_strip_tracking_param(key):
tracking_params_removed += 1
continue
filtered_query.append((key, value))
normalized_path = parsed.path or ""
if normalized_path == "/":
normalized_path = ""
normalized_url = urllib_parse.urlunsplit(
(
"https",
netloc,
normalized_path,
urllib_parse.urlencode(filtered_query, doseq=True),
parsed.fragment,
)
)
normalized_qr_text = normalized_url.replace("https://", "", 1)
chars_saved = max(0, len(raw_input) - len(normalized_url))
return {
"original_input": raw_input,
"normalized_url": normalized_url,
"normalized_qr_text": normalized_qr_text,
"tracking_params_removed": tracking_params_removed,
"chars_saved": chars_saved,
"changed": normalized_url != raw_input,
}
def _normalized_hash(normalized_url: str) -> str:
return hashlib.sha256(str(normalized_url or "").encode("utf-8")).hexdigest()[:32]
def _update_settings_with_new_destination(
settings: Mapping[str, Any] | None,
*,
destination_url: str,
normalized_url: str,
normalized_qr_text: str,
effective_qr_text: str,
short_url: str | None,
shortener_expires_at: str | None,
) -> dict[str, Any]:
updated = copy.deepcopy(dict(settings or {}))
updated["text_input"] = destination_url
updated["input_type"] = "URL"
updated["shortener_applied"] = bool(short_url)
updated["short_url"] = short_url
updated["shortener_expires_at"] = shortener_expires_at
updated["effective_qr_text"] = effective_qr_text
updated["normalized_qr_text"] = normalized_qr_text
updated["url_normalization_applied"] = normalized_url != destination_url
updated["url_tracking_params_removed"] = int(_normalize_url_for_dashboard(destination_url).get("tracking_params_removed") or 0)
updated["url_chars_saved"] = int(_normalize_url_for_dashboard(destination_url).get("chars_saved") or 0)
return updated
def update_generation_destination(
request: Any | None,
records: list[dict[str, Any]],
index: int,
new_destination: str,
username_hint: str | None = None,
) -> dict[str, Any]:
if index < 0 or index >= len(records):
raise DashboardError("Selected QR code is out of range.")
record = records[index]
_authorize_record_access(request, record, username_hint=username_hint)
if str(record.get("input_type") or "") != "URL":
raise DashboardError("Only URL-based QR codes can be retargeted.")
short_link_id = _extract_short_link_id(record)
if not short_link_id:
raise DashboardError("This QR does not use a temporary short link, so there is no redirect target to update.")
normalization = _normalize_url_for_dashboard(new_destination)
destination_url = str(normalization.get("original_input") or "").strip()
normalized_url = str(normalization.get("normalized_url") or "").strip()
normalized_qr_text = str(normalization.get("normalized_qr_text") or normalized_url or "").strip()
if not destination_url or not normalized_url:
raise DashboardError("Enter a valid destination URL.")
existing_short_url = str(record.get("short_url") or "").strip() or None
existing_effective_qr_text = str(record.get("effective_qr_text") or "").strip()
effective_qr_text = existing_effective_qr_text or normalized_qr_text
now = datetime.now(timezone.utc)
expires_at_iso = (now + timedelta(days=7)).isoformat()
short_link_rows = _patch_rows(
"short_links",
filters=f"id=eq.{_quote_filter(short_link_id)}",
values={
"original_url": destination_url,
"normalized_url": normalized_url,
"normalized_url_hash": _normalized_hash(normalized_url),
"last_accessed_at": now.isoformat(),
"expires_at": expires_at_iso,
"is_active": True,
},
)
if not short_link_rows:
raise DashboardError("Short link not found or could not be updated.")
generation_values = {
"destination_url": destination_url,
"destination_url_normalized": normalized_url,
"updated_at": datetime.now(timezone.utc).isoformat(),
}
_patch_rows(
"generations",
filters=f"short_link_id=eq.{_quote_filter(short_link_id)}",
values=generation_values,
)
generation_rows = _table_select(
"generations",
query=f"select=id,metadata&short_link_id=eq.{_quote_filter(short_link_id)}",
)
metadata_by_id = {str(row.get("id") or ""): row.get("metadata") for row in generation_rows}
updated_records = copy.deepcopy(records)
for idx, row in enumerate(updated_records):
row_short_link_id = _extract_short_link_id(row)
if row_short_link_id != short_link_id:
continue
row["destination_url_original"] = destination_url
row["destination_url_normalized"] = normalized_url
row["effective_qr_text"] = effective_qr_text
if existing_short_url:
row["short_url"] = existing_short_url
if expires_at_iso:
row["shortener_expires_at"] = expires_at_iso
metadata = metadata_by_id.get(str(row.get("id") or ""), row.get("metadata"))
metadata_map: dict[str, Any] = copy.deepcopy(dict(metadata or {})) if isinstance(metadata, Mapping) else {}
settings = metadata_map.get("settings") if isinstance(metadata_map.get("settings"), Mapping) else {}
metadata_map["settings"] = _update_settings_with_new_destination(
settings,
destination_url=destination_url,
normalized_url=normalized_url,
normalized_qr_text=normalized_qr_text,
effective_qr_text=effective_qr_text,
short_url=existing_short_url,
shortener_expires_at=expires_at_iso,
)
row["metadata"] = metadata_map
_patch_rows(
"generations",
filters=f"id=eq.{_quote_filter(str(row.get('id') or ''))}",
values={"metadata": metadata_map},
)
detail = get_generation_detail(request, updated_records, index, username_hint=username_hint)
return {
"records": updated_records,
"detail_markdown": detail["detail_markdown"],
"image": detail["image"],
"scan_breakdown_rows": detail["scan_breakdown_rows"],
"settings_json": detail["settings_json"],
"is_standard": detail["is_standard"],
"is_artistic": detail["is_artistic"],
"normalized_url": normalized_url,
"short_url": existing_short_url,
}
def build_generation_table_rows(
records: list[dict[str, Any]],
*,
page: int = 1,
limit: int = 10,
) -> tuple[list[list[Any]], list[int], str, int, int]:
safe_limit = max(1, int(limit))
total_records = len(records)
total_pages = max(1, math.ceil(total_records / safe_limit))
safe_page = min(max(1, int(page)), total_pages)
start_idx = (safe_page - 1) * safe_limit
end_idx = start_idx + safe_limit
visible_records = list(records[start_idx:end_idx])
row_map = list(range(start_idx, start_idx + len(visible_records)))
table_rows: list[list[Any]] = []
for position, record in enumerate(visible_records, start=start_idx + 1):
created = str(record.get("created_at") or "")[:16].replace("T", " ")
qr_mode = str(record.get("qr_mode") or "qr").title()
destination = str(record.get("destination_url_original") or "β€”")
short_url = str(record.get("short_url") or "β€”")
status = str(record.get("status") or "unknown")
table_rows.append([position, created, qr_mode, destination, short_url, status])
if total_records:
shown_start = start_idx + 1
shown_end = start_idx + len(visible_records)
range_label = f"Showing {shown_start}-{shown_end} of {total_records}"
else:
range_label = "Showing 0-0 of 0"
summary = (
f"{range_label} saved QR code(s). "
f"Page {safe_page} of {total_pages}. Click a row to inspect it."
)
return table_rows, row_map, summary, safe_page, total_pages
def resolve_generation_index_from_table_event(
row_map: list[int],
event_index: Any,
) -> int | None:
row_idx = event_index
if isinstance(event_index, (tuple, list)):
row_idx = event_index[0] if event_index else None
if row_idx is None:
return None
try:
row_number = int(row_idx)
except (TypeError, ValueError):
return None
if row_number < 0 or row_number >= len(row_map):
return None
return int(row_map[row_number])
def get_generation_detail(
request: Any | None,
records: list[dict[str, Any]],
index: int,
username_hint: str | None = None,
) -> dict[str, Any]:
if index < 0 or index >= len(records):
raise DashboardError("Selected QR code is out of range.")
record = records[index]
_authorize_record_access(request, record, username_hint=username_hint)
short_link_id = _extract_short_link_id(record)
scan_rows: list[dict[str, Any]] = []
if short_link_id:
scan_rows = _table_select(
"short_link_scan_events",
query=(
"select=scanned_at,country_code,visitor_hash,is_bot,is_prefetch"
f"&short_link_id=eq.{_quote_filter(short_link_id)}"
"&order=scanned_at.desc"
"&limit=1000"
),
)
created_at_dt = _parse_timestamp(record.get("created_at"))
filtered_scan_rows = [
row
for row in scan_rows
if not bool(row.get("is_bot"))
and not bool(row.get("is_prefetch"))
and (
created_at_dt is None
or (_parse_timestamp(row.get("scanned_at")) or created_at_dt) >= created_at_dt
)
]
total_scans = len(filtered_scan_rows)
last_scanned_at = (
str(filtered_scan_rows[0].get("scanned_at") or "").replace("T", " ")[:19]
if filtered_scan_rows
else "Never"
)
unique_scans = len(
{
str(row.get("visitor_hash") or "")
for row in filtered_scan_rows
if str(row.get("visitor_hash") or "").strip()
}
)
scan_breakdown = Counter()
for row in filtered_scan_rows:
scanned_at_raw = str(row.get("scanned_at") or "")
scanned_at_dt = _parse_timestamp(scanned_at_raw) or created_at_dt or datetime.now(timezone.utc)
country = str(row.get("country_code") or "Unknown")
day = scanned_at_dt.strftime("%Y-%m-%d")
hour = scanned_at_dt.strftime("%H:00")
scan_breakdown[(country, day, hour)] += 1
scan_breakdown_rows = [
[country, day, hour, scan_breakdown[(country, day, hour)]]
for country, day, hour in sorted(scan_breakdown.keys(), key=lambda item: (item[1], item[2], item[0]))
]
metadata = record.get("metadata")
metadata_map: Mapping[str, Any] = metadata if isinstance(metadata, Mapping) else {}
raw_settings = metadata_map.get("settings")
settings: Mapping[str, Any] = raw_settings if isinstance(raw_settings, Mapping) else {}
settings_json = json.dumps(settings, indent=2, ensure_ascii=False) if settings else "{}"
detail_markdown = "\n".join(
[
f"### {str(record.get('qr_mode') or 'QR').title()} QR",
f"- Created: {str(record.get('created_at') or '').replace('T', ' ')[:19]}",
f"- Status: {record.get('status') or 'unknown'}",
f"- Input type: {record.get('input_type') or 'unknown'}",
f"- Destination: {record.get('destination_url_original') or 'β€”'}",
f"- Short URL: {record.get('short_url') or 'β€”'}",
f"- Effective QR text: {record.get('effective_qr_text') or 'β€”'}",
f"- Total scans since this QR was created: {total_scans}",
f"- Approx. unique scans since creation: {unique_scans}",
f"- Last scanned at: {last_scanned_at}",
]
)
return {
"record": record,
"detail_markdown": detail_markdown,
"image": record.get("completed_image_url") or record.get("thumbnail_url"),
"scan_breakdown_rows": scan_breakdown_rows,
"settings_json": settings_json,
"is_standard": str(record.get("qr_mode") or "").lower() == "standard",
"is_artistic": str(record.get("qr_mode") or "").lower() == "artistic",
}