From da5177c950ce9d4d9c074d5fda8fc5fff7d048d7 Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Fri, 12 Jun 2026 00:13:27 -0700 Subject: [PATCH] =?UTF-8?q?feat(products):=20hosted-image=20URL=20build/pa?= =?UTF-8?q?rse=20helpers=20(SD-0002=20=C2=A76.3.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DB-free host-agnostic helpers: image_url() builds the canonical hosted Image Src for a fetched image; parse_image_id() recognizes one on re-import via path-parse so exports round-trip across localhost/PPE/rebrand (INV-12). Co-Authored-By: Claude Fable 5 --- backend/app/domains/products/hosted.py | 30 ++++++++++++++++++++++++++ backend/tests/test_products_hosted.py | 30 ++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 backend/app/domains/products/hosted.py create mode 100644 backend/tests/test_products_hosted.py diff --git a/backend/app/domains/products/hosted.py b/backend/app/domains/products/hosted.py new file mode 100644 index 0000000..f667f85 --- /dev/null +++ b/backend/app/domains/products/hosted.py @@ -0,0 +1,30 @@ +"""Hosted-image URL helpers (SD-0002 §6.3.1, INV-12). + +Build the canonical hosted Image Src for a fetched image, and recognize one on +re-import. DB-free and host-agnostic: recognition is a path-parse, so an export +round-trips across localhost / PPE / the #23 rebrand. The diff resolves a parsed +id against the storefront-scoped catalog snapshot. +""" +from __future__ import annotations + +import re +from urllib.parse import urlsplit + +RENDITIONS = ("original", "thumb", "card", "detail") +EXPORT_RENDITION = "detail" + +_PATH = re.compile(r"^/api/products/images/(\d+)/(original|thumb|card|detail)$") + + +def image_url(base_url: str, image_id: int, rendition: str = EXPORT_RENDITION) -> str: + """Absolute when base_url is set, else a root-relative path.""" + path = f"/api/products/images/{image_id}/{rendition}" + return f"{base_url.rstrip('/')}{path}" if base_url else path + + +def parse_image_id(url: str) -> int | None: + """The image id if url is one of our hosted-image URLs (any host), else None.""" + if not url: + return None + m = _PATH.match(urlsplit(url).path) + return int(m.group(1)) if m else None diff --git a/backend/tests/test_products_hosted.py b/backend/tests/test_products_hosted.py new file mode 100644 index 0000000..9c145cb --- /dev/null +++ b/backend/tests/test_products_hosted.py @@ -0,0 +1,30 @@ +"""hosted-image URL helpers — round-trip recognition (SD-0002 §6.3.1, INV-12).""" +from app.domains.products import hosted + + +def test_build_relative_when_no_base(): + assert hosted.image_url("", 42, "detail") == "/api/products/images/42/detail" + + +def test_build_absolute_with_base(): + url = hosted.image_url("https://ecomm-ppe.wiggleverse.org", 42, "detail") + assert url == "https://ecomm-ppe.wiggleverse.org/api/products/images/42/detail" + + +def test_parse_absolute_hosted_url(): + url = "https://market.wiggleverse.org/api/products/images/7/detail" + assert hosted.parse_image_id(url) == 7 + + +def test_parse_relative_hosted_url(): + assert hosted.parse_image_id("/api/products/images/7/card") == 7 + + +def test_parse_ignores_query_and_trailing(): + assert hosted.parse_image_id("/api/products/images/7/detail?v=1") == 7 + + +def test_parse_non_hosted_returns_none(): + assert hosted.parse_image_id("https://cdn.example.com/a/b.png") is None + assert hosted.parse_image_id("/api/products/images/abc/detail") is None + assert hosted.parse_image_id("/api/products/images/7") is None