Files
tehran_taj/apps/api/prisma/assign-downloaded-images.js
T
s.esmaeili b80fa9bcfb Redesign product detail page and fix icon/cart reactivity bugs
- SingleProduct.vue: rebuild gallery/header/action bar as a self-scrolling
  layout with a sticky photo, solid-on-scroll header matching PageBar, and
  a full-screen image preview (blurred overlay, centered thumbnail strip)
- Add GallerySlider.vue: pointer-based swipe carousel isolated in an
  LTR subtree, fixing RTL/transform sign mismatch that caused slide jumps
- Fix Icon.vue: icon was read into a plain ref once at setup and never
  updated on prop change; switch to computed so icon swaps (e.g. cart
  qty stepper trash/minus toggle) actually re-render
- Fix quantity stepper DOM order in SingleProduct.vue/CartItem.vue so
  plus/minus render correctly under RTL flex
- prisma/assign-downloaded-images.js: seed real product photos (FEATURED/
  FEATURED_MOBILE/GALLERY/GALLERY_MOBILE) onto the 5 placeholder products
2026-09-18 16:22:31 +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());