feat: clear Shopify IDs from Sheets to allow product recreation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 15:05:43 +02:00
parent cdde567558
commit 34ade6beb2
2 changed files with 123 additions and 4 deletions
+93
View File
@@ -0,0 +1,93 @@
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
async function getAccessToken(): Promise<string | null> {
const email = process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL
const rawKey = process.env.GOOGLE_SERVICE_ACCOUNT_KEY
if (!email || !rawKey) return null
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 null
const { access_token } = await tokenRes.json() as { access_token: string }
return access_token
}
// POST /api/creation/clear-ids?tab=SOL CARRELAGE
// Efface la colonne A (ID Shopify) de toutes les lignes produit d'un onglet
export async function POST(req: NextRequest) {
const session = await getServerSession(authOptions)
if (!session) return NextResponse.json({ error: 'Non authentifié' }, { status: 401 })
const tab = new URL(req.url).searchParams.get('tab')
if (!tab) return NextResponse.json({ error: 'tab requis' }, { status: 400 })
const apiKey = process.env.GOOGLE_API_KEY
const sheetId = process.env.GOOGLE_SHEETS_ID
if (!apiKey || !sheetId) return NextResponse.json({ error: 'Config Google manquante' }, { status: 500 })
// Lire les données de l'onglet
const range = encodeURIComponent(`${tab}!A1:A500`)
const res = await fetch(
`https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${range}?key=${apiKey}`,
)
if (!res.ok) return NextResponse.json({ error: `Sheets read error: ${res.status}` }, { status: 502 })
const data = await res.json() as { values?: string[][] }
const rows = data.values ?? []
// Trouver la ligne d'en-tête (contient "ID" en col A)
const headerRowIdx = rows.findIndex(r => (r[0] ?? '').trim() === 'ID')
if (headerRowIdx === -1) return NextResponse.json({ error: 'Onglet non reconnu (pas de colonne ID)' }, { status: 400 })
// Identifier les lignes produit qui ont un ID Shopify (numérique)
const clearValues: string[][] = []
const startRow = headerRowIdx + 2 // 1-indexed, skip header row
for (let i = headerRowIdx + 1; i < rows.length; i++) {
const cell = (rows[i]?.[0] ?? '').trim()
clearValues.push([cell && /^\d+$/.test(cell) ? '' : cell])
}
if (clearValues.length === 0) return NextResponse.json({ cleared: 0 })
const accessToken = await getAccessToken()
if (!accessToken) return NextResponse.json({ error: 'Impossible d\'obtenir le token Google' }, { status: 500 })
const writeRange = `${tab}!A${startRow}:A${startRow + clearValues.length - 1}`
const writeRes = await fetch(
`https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${encodeURIComponent(writeRange)}?valueInputOption=RAW`,
{
method: 'PUT',
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ range: writeRange, majorDimension: 'ROWS', values: clearValues }),
},
)
if (!writeRes.ok) {
const err = await writeRes.text()
return NextResponse.json({ error: `Sheets write error: ${err}` }, { status: 502 })
}
const cleared = clearValues.filter(r => r[0] === '').length
return NextResponse.json({ cleared, tab })
}