From cc0f9cc3af9f02c948c849872c00e64a917cdc46 Mon Sep 17 00:00:00 2001 From: Aviram Hassan Date: Wed, 10 Jun 2026 10:00:26 +0000 Subject: [PATCH] Fix product 2 image by normalizing deprecated catalog URLs Product 2 in the shared inventory DB still references a removed Cloudinary path (Metal Mart/samples/mirrord-hoodie-front). Map that legacy value to the current front/back public IDs at the API layer and filter blank image_urls entries in the frontend helpers. --- apps/shop/inventory-service/src/index.ts | 51 +++++++++++++++++-- .../metal-mart-frontend/src/lib/product.ts | 16 ++++-- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/apps/shop/inventory-service/src/index.ts b/apps/shop/inventory-service/src/index.ts index 469582bd..9aa88c4a 100644 --- a/apps/shop/inventory-service/src/index.ts +++ b/apps/shop/inventory-service/src/index.ts @@ -5,6 +5,49 @@ import { Pool } from "pg"; const app = express(); const port = parseInt(process.env.PORT || "80", 10); +type ProductRow = { + id: number; + name: string; + description: string | null; + price_cents: number; + stock: number; + image_url: string | null; + image_urls: unknown; + is_new: boolean; +}; + +const deprecatedImageReplacements: Record = { + "Metal Mart/samples/mirrord-hoodie-front": [ + "team_Work_makes_the_Dream_Work_-_front_w5qdnb", + "team_work_makes_the_dream_work_-_back_onanux", + ], +}; + +function normalizeImageValue(value: unknown): string[] { + if (typeof value !== "string") return []; + + const trimmed = value.trim(); + if (!trimmed) return []; + + return deprecatedImageReplacements[trimmed] ?? [trimmed]; +} + +function normalizeImageUrls(imageUrls: unknown, imageUrl: unknown): string[] { + const urls = Array.isArray(imageUrls) ? imageUrls.flatMap(normalizeImageValue) : []; + if (urls.length > 0) return urls; + + return normalizeImageValue(imageUrl); +} + +function normalizeProductRow(row: ProductRow) { + const image_urls = normalizeImageUrls(row.image_urls, row.image_url); + return { + ...row, + image_url: image_urls[0] ?? null, + image_urls, + }; +} + let dbUrl = process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/inventory"; // mirrord branch DB URLs may omit the database name — ensure we connect to "inventory" if (dbUrl && !/:\d+\/.+$/.test(dbUrl)) { @@ -72,8 +115,8 @@ app.get("/health", (_req, res) => { app.get("/products", async (_req, res) => { // Set a breakpoint here; trigger with: curl http://localhost:28080/products -H "X-PG-Tenant: dev" (while port-forward + mirrord are running) try { - const { rows } = await pool.query("SELECT id, name, description, price_cents, stock, image_url, image_urls, is_new FROM products ORDER BY id"); - res.json(rows); + const { rows } = await pool.query("SELECT id, name, description, price_cents, stock, image_url, image_urls, is_new FROM products ORDER BY id"); + res.json(rows.map(normalizeProductRow)); } catch (err) { console.error("Error fetching products:", err); res.status(500).json({ error: "Internal server error" }); @@ -86,14 +129,14 @@ app.get("/products/:id", async (req, res) => { return res.status(400).json({ error: "Invalid product ID" }); } try { - const { rows } = await pool.query( + const { rows } = await pool.query( "SELECT id, name, description, price_cents, stock, image_url, image_urls, is_new FROM products WHERE id = $1", [id] ); if (rows.length === 0) { return res.status(404).json({ error: "Product not found" }); } - res.json(rows[0]); + res.json(normalizeProductRow(rows[0])); } catch (err) { console.error("Error fetching product:", err); res.status(500).json({ error: "Internal server error" }); diff --git a/apps/shop/metal-mart-frontend/src/lib/product.ts b/apps/shop/metal-mart-frontend/src/lib/product.ts index 6612bd2f..3681f553 100644 --- a/apps/shop/metal-mart-frontend/src/lib/product.ts +++ b/apps/shop/metal-mart-frontend/src/lib/product.ts @@ -10,17 +10,23 @@ export type Product = { is_new?: boolean; }; +function normalizeImageUrl(url: string | null | undefined): string | null { + const trimmed = url?.trim(); + return trimmed ? trimmed : null; +} + /** Primary image for thumbnails (first in array, or legacy image_url). */ export function getPrimaryImageUrl(product: Product): string | null { - const urls = product.image_urls; - if (urls && urls.length > 0) return urls[0]; - return product.image_url ?? null; + return getImageUrls(product)[0] ?? null; } /** All image URLs for product (front, back, etc.). */ export function getImageUrls(product: Product): string[] { - const urls = product.image_urls; + const urls = product.image_urls + ?.map((url) => normalizeImageUrl(url)) + .filter((url): url is string => url !== null); if (urls && urls.length > 0) return urls; - const single = product.image_url; + + const single = normalizeImageUrl(product.image_url); return single ? [single] : []; }