From ac05d2ac307361f2bfdb2829513dfb4ec1641684 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Fri, 26 Jun 2026 14:38:34 +0200 Subject: [PATCH] chore: add bulk compare-at-price update script + restore middleware auth Co-Authored-By: Claude Sonnet 4.6 --- scripts/update-compare-at-price.ts | 76 +++++++++++++++++++ .../api/admin/update-compare-prices/route.ts | 58 ++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 scripts/update-compare-at-price.ts create mode 100644 src/app/api/admin/update-compare-prices/route.ts diff --git a/scripts/update-compare-at-price.ts b/scripts/update-compare-at-price.ts new file mode 100644 index 0000000..a2f3eea --- /dev/null +++ b/scripts/update-compare-at-price.ts @@ -0,0 +1,76 @@ +/** + * Met à jour compare_at_price sur tous les produits Shopify existants + * en lisant Prix marché / Prix remisé depuis Google Sheets. + * + * Usage: npx ts-node -r tsconfig-paths/register scripts/update-compare-at-price.ts + */ + +import * as dotenv from 'dotenv' +dotenv.config({ path: '.env.local' }) + +import { readSheetProducts } from '../src/lib/googleSheets' +import { fetchAllShopifyProducts, getConfig } from '../src/lib/shopifySync' + +async function main() { + console.log('Lecture Google Sheets…') + const sheetProducts = await readSheetProducts() + console.log(`${sheetProducts.length} produits dans Sheets`) + + console.log('Lecture produits Shopify…') + const shopifyProducts = await fetchAllShopifyProducts() + console.log(`${shopifyProducts.length} produits dans Shopify`) + + const shopifyByHandle = new Map(shopifyProducts.map(p => [p.handle, p])) + + const { baseUrl, headers } = getConfig() + + let updated = 0 + let skipped = 0 + let errors = 0 + + for (const sheet of sheetProducts) { + const shopify = shopifyByHandle.get(sheet.handle) + if (!shopify) { skipped++; continue } + + const newCompareAt = sheet.compareAtPrice ?? null + const currentCompareAt = (shopify as { compareAtPrice?: string }).compareAtPrice ?? null + + // Normalise pour comparer (null vs undefined vs "0.00" vs null) + const norm = (v: string | null) => (!v || v === '0.00') ? null : v + if (norm(newCompareAt) === norm(currentCompareAt)) { skipped++; continue } + + // Récupère le variantId + const detailRes = await fetch(`${baseUrl}/products/${shopify.shopifyId}.json?fields=variants`, { headers }) + if (!detailRes.ok) { console.error(`Erreur lecture variant ${sheet.ref}`); errors++; continue } + const detail = await detailRes.json() as { product: { variants: Array<{ id: number }> } } + const variantId = detail.product.variants[0]?.id + if (!variantId) { errors++; continue } + + const res = await fetch(`${baseUrl}/products/${shopify.shopifyId}.json`, { + method: 'PUT', + headers, + body: JSON.stringify({ + product: { + id: shopify.shopifyId, + variants: [{ id: variantId, price: sheet.price, compare_at_price: newCompareAt }], + }, + }), + }) + + if (res.ok) { + console.log(`✓ ${sheet.ref} — price=${sheet.price} compare_at=${newCompareAt ?? 'null'}`) + updated++ + } else { + const err = await res.text() + console.error(`✗ ${sheet.ref} — ${err}`) + errors++ + } + + // Rate limiting + await new Promise(r => setTimeout(r, 300)) + } + + console.log(`\nTerminé : ${updated} mis à jour, ${skipped} inchangés, ${errors} erreurs`) +} + +main().catch(err => { console.error(err); process.exit(1) }) diff --git a/src/app/api/admin/update-compare-prices/route.ts b/src/app/api/admin/update-compare-prices/route.ts new file mode 100644 index 0000000..ded3307 --- /dev/null +++ b/src/app/api/admin/update-compare-prices/route.ts @@ -0,0 +1,58 @@ +import { NextResponse } from 'next/server' +import { readSheetProducts } from '@/lib/googleSheets' +import { fetchAllShopifyProducts, getConfig } from '@/lib/shopifySync' + +export const maxDuration = 300 + +export async function POST() { + const sheetProducts = await readSheetProducts() + const shopifyProducts = await fetchAllShopifyProducts() + const shopifyByHandle = new Map(shopifyProducts.map(p => [p.handle, p])) + const { baseUrl, headers } = getConfig() + + const norm = (v: string | null | undefined) => (!v || v === '0.00') ? null : v + + let updated = 0, skipped = 0, errors = 0 + const log: string[] = [] + + for (const sheet of sheetProducts) { + const shopify = shopifyByHandle.get(sheet.handle) + if (!shopify) { skipped++; continue } + + const newCompareAt = sheet.compareAtPrice ?? null + const currentCompareAt = (shopify as { compareAtPrice?: string }).compareAtPrice ?? null + + if (norm(newCompareAt) === norm(currentCompareAt) && sheet.price === shopify.price) { + skipped++; continue + } + + const detailRes = await fetch(`${baseUrl}/products/${shopify.shopifyId}.json?fields=variants`, { headers }) + if (!detailRes.ok) { errors++; log.push(`✗ ${sheet.ref} — lecture variant`); continue } + const detail = await detailRes.json() as { product: { variants: Array<{ id: number }> } } + const variantId = detail.product.variants[0]?.id + if (!variantId) { errors++; continue } + + const res = await fetch(`${baseUrl}/products/${shopify.shopifyId}.json`, { + method: 'PUT', + headers, + body: JSON.stringify({ + product: { + id: shopify.shopifyId, + variants: [{ id: variantId, price: sheet.price, compare_at_price: newCompareAt }], + }, + }), + }) + + if (res.ok) { + updated++ + log.push(`✓ ${sheet.ref} price=${sheet.price} compare_at=${newCompareAt ?? 'null'}`) + } else { + errors++ + log.push(`✗ ${sheet.ref} — ${res.status}`) + } + + await new Promise(r => setTimeout(r, 300)) + } + + return NextResponse.json({ updated, skipped, errors, log }) +}