import type { SyncProduct } from '@/types/sync' function normalizeHandle(ref: string): string { return ref .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') } /** * Parse des lignes brutes Google Sheets en SyncProduct[]. * Colonnes : A=Référence, B=Nom, C=Description, D=Prix, E=Stock, F=Statut * Exporté pour les tests unitaires. */ const HEADER_WORDS = new Set(['id', 'ref', 'sku', 'nom', 'titre', 'reference', 'prix', 'statut']) function isValidRef(ref: string): boolean { if (ref.length < 2) return false if (/^[^a-zA-Z0-9]+$/.test(ref)) return false // que des caractères spéciaux if (HEADER_WORDS.has(ref.toLowerCase())) return false return true } export function parseSheetRows(rows: string[][]): SyncProduct[] { return rows .map((row): SyncProduct | null => { const ref = row[0]?.trim() if (!ref || !isValidRef(ref)) return null const rawPrice = (row[3] ?? '').replace(',', '.').trim() const price = parseFloat(rawPrice || '0') // Ligne sans prix valide = séparateur ou note, pas un produit if (isNaN(price) || price <= 0) return null return { ref, handle: normalizeHandle(ref), title: row[1]?.trim() || ref, description: row[2]?.trim() || '', price: price.toFixed(2), stock: parseInt(row[4] ?? '0', 10) || 0, status: (row[5] ?? '').toLowerCase().includes('inac') ? 'draft' : 'active', } }) .filter((p): p is SyncProduct => p !== null) } /** * Lit tous les produits depuis Google Sheets via l'API v4 (API Key publique). * Si GOOGLE_SHEETS_TAB est défini, lit uniquement cet onglet. * Sinon, lit tous les onglets du fichier et combine les produits. * Le fichier Sheets doit être partagé en lecture ("Toute personne avec le lien"). */ export async function readSheetProducts(): Promise { const apiKey = process.env.GOOGLE_API_KEY const sheetId = process.env.GOOGLE_SHEETS_ID const singleTab = process.env.GOOGLE_SHEETS_TAB if (!apiKey || !sheetId) { throw new Error('Variables GOOGLE_API_KEY et GOOGLE_SHEETS_ID requises') } const sheetNames = singleTab ? [singleTab] : await listSheetTabs(sheetId, apiKey) const results = await Promise.all( sheetNames.map((name) => readOneTab(sheetId, apiKey, name)), ) return results.flat() } async function listSheetTabs(sheetId: string, apiKey: string): Promise { const url = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}?key=${apiKey}&fields=sheets.properties.title` const res = await fetch(url) if (!res.ok) { const err = await res.text() throw new Error(`Google Sheets API error: ${res.status} — ${err}`) } const data = await res.json() return (data.sheets as Array<{ properties: { title: string } }>).map( (s) => s.properties.title, ) } async function readOneTab(sheetId: string, apiKey: string, sheetName: string): Promise { const range = encodeURIComponent(`${sheetName}!A2:F`) const url = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${range}?key=${apiKey}` const res = await fetch(url) if (!res.ok) { const err = await res.text() throw new Error(`Google Sheets API error (onglet "${sheetName}"): ${res.status} — ${err}`) } const data = await res.json() return parseSheetRows((data.values ?? []) as string[][]) }