feat: client Google Sheets + parseSheetRows (TDD)
Implémente la lecture depuis Google Sheets avec la fonction de parsing en TDD. - parseSheetRows(): parseur pur, testé avec 7 cas (tests unitaires) - readSheetProducts(): client async qui utilise l'API v4 de Google Sheets - Normalisation des handles (minuscules, tirets, trim) - Gestion des prix français (virgule → point), status inactif → draft - Valeurs par défaut pour colonnes manquantes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import type { SyncProduct } from '@/types/sync'
|
||||
|
||||
const API_VERSION = 'v4'
|
||||
|
||||
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.
|
||||
*/
|
||||
export function parseSheetRows(rows: string[][]): SyncProduct[] {
|
||||
return rows
|
||||
.map((row): SyncProduct | null => {
|
||||
const ref = row[0]?.trim()
|
||||
if (!ref) return null
|
||||
|
||||
const rawPrice = (row[3] ?? '').replace(',', '.').trim()
|
||||
const price = parseFloat(rawPrice || '0')
|
||||
|
||||
return {
|
||||
ref,
|
||||
handle: normalizeHandle(ref),
|
||||
title: row[1]?.trim() || ref,
|
||||
description: row[2]?.trim() || '',
|
||||
price: isNaN(price) ? '0.00' : 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.
|
||||
* Utilise un service account (GOOGLE_SERVICE_ACCOUNT_EMAIL + GOOGLE_PRIVATE_KEY).
|
||||
*/
|
||||
export async function readSheetProducts(): Promise<SyncProduct[]> {
|
||||
const email = process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL
|
||||
const key = process.env.GOOGLE_PRIVATE_KEY?.replace(/\\n/g, '\n')
|
||||
const sheetId = process.env.GOOGLE_SHEETS_ID
|
||||
const sheetName = process.env.GOOGLE_SHEETS_TAB ?? 'Produits'
|
||||
|
||||
if (!email || !key || !sheetId) {
|
||||
throw new Error('Variables GOOGLE_SERVICE_ACCOUNT_EMAIL, GOOGLE_PRIVATE_KEY et GOOGLE_SHEETS_ID requises')
|
||||
}
|
||||
|
||||
const { google } = await import('googleapis')
|
||||
|
||||
const auth = new google.auth.GoogleAuth({
|
||||
credentials: { client_email: email, private_key: key },
|
||||
scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'],
|
||||
})
|
||||
|
||||
const sheets = google.sheets({ version: API_VERSION, auth })
|
||||
|
||||
const response = await sheets.spreadsheets.values.get({
|
||||
spreadsheetId: sheetId,
|
||||
range: `${sheetName}!A2:F`, // A2 = skip header row
|
||||
})
|
||||
|
||||
const rows = (response.data.values ?? []) as string[][]
|
||||
return parseSheetRows(rows)
|
||||
}
|
||||
Reference in New Issue
Block a user