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
+30 -4
View File
@@ -14,6 +14,8 @@ export default function CreationPage() {
const [selectedTab, setSelectedTab] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [clearingIds, setClearingIds] = useState(false)
const [clearResult, setClearResult] = useState<string | null>(null)
const [byTab, setByTab] = useState<Record<string, PendingProduct[]>>({})
const [total, setTotal] = useState(0)
const [specificColumns, setSpecificColumns] = useState<string[]>([])
@@ -88,6 +90,23 @@ export default function CreationPage() {
]
const currentIdx = STEPS.findIndex(s => s.id === step)
const clearSheetIds = async () => {
if (!selectedTab) return
if (!confirm(`Effacer tous les IDs Shopify dans l'onglet "${selectedTab}" ? Les produits Shopify ne seront PAS supprimés.`)) return
setClearingIds(true)
setClearResult(null)
try {
const res = await fetch(`/api/creation/clear-ids?tab=${encodeURIComponent(selectedTab)}`, { method: 'POST' })
const data = await res.json()
if (!res.ok) throw new Error(data.error)
setClearResult(`${data.cleared} ID${data.cleared > 1 ? 's' : ''} effacé${data.cleared > 1 ? 's' : ''} dans le Sheets`)
} catch (e) {
setError(e instanceof Error ? e.message : 'Erreur')
} finally {
setClearingIds(false)
}
}
const reset = () => {
setStep('famille')
setSelectedTab(null)
@@ -133,10 +152,17 @@ export default function CreationPage() {
<p className="text-gray-300 text-sm">
Détecte les lignes de <strong className="text-white">{selectedTab}</strong> <code className="text-indigo-300">Publié=1</code> et <code className="text-indigo-300">ID vide</code>.
</p>
<button onClick={analyse} disabled={loading}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50 text-white rounded-lg text-sm font-medium transition-colors">
{loading ? 'Analyse en cours…' : 'Analyser le Sheets'}
</button>
<div className="flex gap-3 flex-wrap items-center">
<button onClick={analyse} disabled={loading}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50 text-white rounded-lg text-sm font-medium transition-colors">
{loading ? 'Analyse en cours…' : 'Analyser le Sheets'}
</button>
<button onClick={clearSheetIds} disabled={clearingIds}
className="px-4 py-2 bg-amber-700 hover:bg-amber-600 disabled:opacity-50 text-white rounded-lg text-sm font-medium transition-colors">
{clearingIds ? 'Nettoyage…' : '🗑 Vider les IDs Shopify du Sheets'}
</button>
</div>
{clearResult && <p className="text-green-400 text-xs">{clearResult}</p>}
</div>
)}
+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 })
}