Files
hedena_gold/apps/api/prisma/assign-downloaded-images.js
T
s.esmaeili ea86636027 Initial commit: Hedena Gold monorepo.
First commit of the Hedena Gold product (separate from gold-bank): branding, package names, and deploy defaults aligned to the new brand; legacy Henza trees stay local-only via .gitignore.
2026-10-15 17:19:10 +03:30

135 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// One-off data fix: the 5 hand-authored demo products (product-classic-ring,
// product-woven-bracelet, product-pendant-necklace, product-hoop-earrings,
// product-full-coin) each had exactly one ProductImage row pointing at
// /img/no-items.png — a missing-image placeholder, not a real photo. This
// replaces those with a real, varied set drawn from the 15 jewelry photos the
// user pointed at (originally in "Downloads/New folder", already copied into
// storage/uploads/ under their original filenames — confirmed present before
// this script was written).
//
// Distribution, mirroring the one product in this DB that already has a full,
// correctly-roled image set (`شسی`, id cmu4ia4iq0008s4zhwrz6wy3z):
// - exactly one FEATURED (تصویر شاخص وب)
// - exactly one FEATURED_MOBILE (تصویر شاخص موبایل)
// - 2–3 GALLERY / GALLERY_MOBILE, split across both roles
//
// CHECKED, not assumed: CatalogService.listProducts() (apps/api/src/modules/
// catalog/catalog.service.ts) returns every ProductImage row unfiltered —
// there is no role-based split on the read side today. So on the user-app
// side ALL of these rows end up in ProductSummary.images and all of them are
// swipeable in the gallery/preview — the FEATURED/FEATURED_MOBILE role still
// matters (it is what the admin panel and any future "hero image" logic reads
// by role), but right now it does not narrow what the shopper-facing gallery
// shows. `sortOrder` is set to the creation index below, which is what
// actually determines swipe order today, since nothing filters by role.
//
// Assignment across the 5 products is a random, non-repeating draw per
// product from the 15-photo pool (Fisher-Yates, reseedable via IMG_SEED for a
// reproducible run) — the same photo can end up on more than one product
// across different products (15 photos split ~4 ways per product exceeds the
// pool), which is fine: this is demo data standing in for real per-product
// photography, not a claim that these are 5 distinct real items.
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// The 15 files confirmed present in storage/uploads/ under their original
// names before this script ran.
const POOL = [
'152_5e1435ca-05a5-4146-a4ea-89726650c29c.webp',
'59_0661f668-5d1d-4c05-87fd-50fd65cd8383.webp',
'71085964.png',
'79A202-1201_01.webp',
'Brooches-Vintage-Jewelry-full-1-322_60.60-9ed8e81b.webp',
'MB116662_Front_1080p_f364da1d-6019-4ca4-8dd8-a323387634c7.webp',
'TEMP_360x360.webp',
'channel_setting_480x480.webp',
'engagement-ring-in-18k-gold-with-diamonds-nr532-white-18k-semi-white-205224.webp',
'finished-jewelry.webp',
'kisspng-earring-gold-jewellery-ring-new-22ct-yellow-gold-ring-armari-45-sku27555-5d0796cb466539.4977043815607784432884-768x768.png',
'pl-background-free-product-photography-jewelery-3.webp',
'round-cut-engagement-ring-in-18k-gold-with-diamonds-lr3011-2t-18k-x-2t-964060.webp',
'td4-aam-n0_dd28f69d-9e3c-490d-be37-1ba074737992.webp',
'w=640,h=640,fit=crop.avif',
];
const PRODUCT_IDS = [
'product-classic-ring',
'product-woven-bracelet',
'product-pendant-necklace',
'product-hoop-earrings',
'product-full-coin',
];
// Simple seeded PRNG (mulberry32) so a re-run with the same IMG_SEED is
// reproducible — useful for re-checking a specific assignment without the
// draw changing under you.
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const seed = Number(process.env.IMG_SEED) || Date.now();
const rand = mulberry32(seed);
function shuffled(arr) {
const a = arr.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(rand() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
async function main() {
console.log(`seed=${seed}`);
for (const productId of PRODUCT_IDS) {
const product = await prisma.product.findUnique({ where: { id: productId } });
if (!product) {
console.log(`skip ${productId}: not found`);
continue;
}
// 3–5 photos per product, drawn without replacement from the pool.
const count = 3 + Math.floor(rand() * 3); // 3, 4, or 5
const picks = shuffled(POOL).slice(0, count);
// Role plan: first pick is FEATURED, second is FEATURED_MOBILE, the rest
// alternate GALLERY / GALLERY_MOBILE — same shape as the شسی product's
// existing 8-image set, just proportioned to a smaller pick count.
const roles = ['FEATURED', 'FEATURED_MOBILE'];
for (let i = 2; i < picks.length; i++) {
roles.push(i % 2 === 0 ? 'GALLERY' : 'GALLERY_MOBILE');
}
// Replace whatever this product currently has (the single no-items.png
// placeholder row) rather than appending alongside it.
await prisma.productImage.deleteMany({ where: { productId } });
for (let i = 0; i < picks.length; i++) {
await prisma.productImage.create({
data: {
id: `${productId}-img-${i + 1}`,
productId,
url: `/uploads/${picks[i]}`,
sortOrder: i,
role: roles[i],
},
});
}
console.log(`${productId}: ${picks.length} images -> ${picks.map((p, i) => `${roles[i]}:${p}`).join(', ')}`);
}
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => prisma.$disconnect());