feat: écriture de l'ID Shopify en col A du Sheets après création produit

Utilise un compte de service Google avec JWT pour authentifier les écritures
dans le Sheets — sans dépendance npm supplémentaire (crypto natif Node.js).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 12:42:17 +02:00
parent 15f647f58a
commit 42d0e2f855
2 changed files with 49 additions and 1 deletions
+2 -1
View File
@@ -1,7 +1,7 @@
import { NextRequest } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { fetchPendingProducts } from '@/lib/creationSheets'
import { fetchPendingProducts, writeShopifyIdToSheet } from '@/lib/creationSheets'
import { generateDescriptions } from '@/lib/openaiClient'
import { ensureMetafieldDefinition, setProductMetafield } from '@/lib/shopifyMetafields'
import { addProductToCollection } from '@/lib/shopifyCollections'
@@ -92,6 +92,7 @@ export async function POST(req: NextRequest) {
await prisma.productCreation.create({
data: { userId, sheetTab: product.tab, sheetRow: product.rowNumber, shopifyId, sku: product.sku, title: product.title, status: 'success' },
})
await writeShopifyIdToSheet(product.tab, product.rowNumber, shopifyId)
created++
} catch (err) {
const message = err instanceof Error ? err.message : 'Erreur inconnue'
+47
View File
@@ -80,6 +80,53 @@ export function parseTabRows(tab: string, headers: string[], rows: string[][], d
.filter((p): p is PendingProduct => p !== null)
}
/**
* Écrit l'ID Shopify en colonne A de la ligne correspondante dans Google Sheets.
* Utilise un compte de service avec accès éditeur sur la feuille.
*/
export async function writeShopifyIdToSheet(tab: string, rowNumber: number, shopifyId: string): Promise<void> {
const sheetId = process.env.GOOGLE_SHEETS_ID
const email = process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL
const rawKey = process.env.GOOGLE_SERVICE_ACCOUNT_KEY
if (!sheetId || !email || !rawKey) return
// Génère un JWT pour l'authentification Google
const privateKey = rawKey.replace(/\\n/g, '\n')
const now = Math.floor(Date.now() / 1000)
const header = Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })).toString('base64url')
const payload = Buffer.from(JSON.stringify({
iss: email,
scope: 'https://www.googleapis.com/auth/spreadsheets',
aud: 'https://oauth2.googleapis.com/token',
iat: now,
exp: now + 3600,
})).toString('base64url')
const { createSign } = await import('crypto')
const sign = createSign('RSA-SHA256')
sign.update(`${header}.${payload}`)
const signature = sign.sign(privateKey, 'base64url')
const jwt = `${header}.${payload}.${signature}`
const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: jwt }),
})
if (!tokenRes.ok) return
const { access_token } = await tokenRes.json() as { access_token: string }
const range = encodeURIComponent(`${tab}!A${rowNumber}`)
await fetch(
`https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${range}?valueInputOption=RAW`,
{
method: 'PUT',
headers: { Authorization: `Bearer ${access_token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ range: `${tab}!A${rowNumber}`, majorDimension: 'ROWS', values: [[shopifyId]] }),
},
)
}
export async function fetchPendingProducts(
alreadyCreatedRows: Set<string>,
tabFilter?: string,