From 1aaf454ba56f3f8f04f95b470ae453d02b3249de Mon Sep 17 00:00:00 2001 From: Christian Vidal Wolf Date: Fri, 10 Apr 2026 11:01:55 +0200 Subject: [PATCH] fix: use raw fetch for Dropbox API instead of SDK, add better error logging --- api/dropbox-proxy.js | 55 ++++++++++++++++++++--------- api/dropbox-sync.js | 83 ++++++++++++++++++++++++-------------------- src/App.tsx | 26 +++++++------- 3 files changed, 98 insertions(+), 66 deletions(-) diff --git a/api/dropbox-proxy.js b/api/dropbox-proxy.js index 895fc94..d61bb01 100644 --- a/api/dropbox-proxy.js +++ b/api/dropbox-proxy.js @@ -1,34 +1,57 @@ -const Dropbox = require('dropbox').Dropbox; +import fetch from 'node:fetch'; + +const DROPBOX_APP_KEY = process.env.DROPBOX_APP_KEY; +const DROPBOX_APP_SECRET = process.env.DROPBOX_APP_SECRET; +const DROPBOX_REFRESH_TOKEN = process.env.DROPBOX_REFRESH_TOKEN; + +async function getAccessToken() { + const response = await fetch('https://api.dropboxapi.com/oauth2/token', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `grant_type=refresh_token&refresh_token=${DROPBOX_REFRESH_TOKEN}&client_id=${DROPBOX_APP_KEY}&client_secret=${DROPBOX_APP_SECRET}` + }); + const data = await response.json(); + return data.access_token; +} export default async function handler(req, res) { + const accessToken = await getAccessToken(); + if (req.method === 'GET' && req.query.info === '1') { - const DBX = new Dropbox({ - accessToken: process.env.DROPBOX_ACCESS_TOKEN - }); - try { - const fileInfo = await DBX.filesGetInfo({ path: '/Data-Matrix.xlsx' }); + const fileInfo = await fetch('https://api.dropboxapi.com/2/files/get_metadata', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ path: '/Data-Matrix.xlsx' }) + }); + const data = await fileInfo.json(); return res.json({ - rev: fileInfo.result.rev, - size: fileInfo.result.size, - server_modified: fileInfo.result.server_modified + rev: data.rev, + size: data.size, + server_modified: data.server_modified }); } catch (err) { return res.status(500).json({ error: err.message }); } } - const DBX = new Dropbox({ - accessToken: process.env.DROPBOX_ACCESS_TOKEN - }); - try { - const upstream = await DBX.filesDownload({ path: '/Data-Matrix.xlsx' }); - const buffer = Buffer.from(upstream.result.fileBinary); + const upstream = await fetch('https://content.dropboxapi.com/2/files/download', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Dropbox-API-Arg': JSON.stringify({ path: '/Data-Matrix.xlsx' }) + } + }); + + const buffer = await upstream.arrayBuffer(); res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Cache-Control', 'no-store'); - res.send(buffer); + res.send(Buffer.from(buffer)); } catch (err) { console.error('Dropbox error:', err); res.status(500).send('Proxy error: ' + err.message); diff --git a/api/dropbox-sync.js b/api/dropbox-sync.js index 170fa86..517c75e 100644 --- a/api/dropbox-sync.js +++ b/api/dropbox-sync.js @@ -6,46 +6,53 @@ const SUPABASE_KEY = process.env.SUPABASE_SERVICE_KEY || 'sb_publishable_fGXkh0b const supabase = createClient(SUPABASE_URL, SUPABASE_KEY); export default async function handler(req, res) { - if (req.method !== 'POST') { - return res.status(405).json({ error: 'Method not allowed' }); - } - - const { rows, fileMeta } = req.body; - - if (!rows || !Array.isArray(rows)) { - return res.status(400).json({ error: 'Missing rows data' }); - } - - const articleNoIdx = 0; - - const productsToUpsert = []; - - for (const row of rows) { - const productId = String(row[articleNoIdx]); - if (productId && productId.trim() !== '') { - productsToUpsert.push({ - product_id: productId, - data: row, - status: 'synced', - updated_at: new Date().toISOString() - }); + try { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); } - } - const { error } = await supabase - .from('products') - .upsert(productsToUpsert, { - onConflict: 'product_id', - ignoreDuplicates: false + const { rows, fileMeta } = req.body; + + if (!rows || !Array.isArray(rows)) { + return res.status(400).json({ error: 'Missing rows data' }); + } + + const articleNoIdx = 0; + + const productsToUpsert = []; + + for (const row of rows) { + const productId = String(row[articleNoIdx]); + if (productId && productId.trim() !== '') { + productsToUpsert.push({ + product_id: productId, + data: row, + status: 'synced', + updated_at: new Date().toISOString() + }); + } + } + + console.log('Upserting', productsToUpsert.length, 'products to Supabase...'); + + const { error } = await supabase + .from('products') + .upsert(productsToUpsert, { + onConflict: 'product_id', + ignoreDuplicates: false + }); + + if (error) { + console.error('Supabase upsert error:', error); + return res.status(500).json({ error: error.message, detail: 'Failed to upsert products' }); + } + + return res.json({ + success: true, + syncedCount: productsToUpsert.length }); - - if (error) { - console.error('Supabase upsert error:', error); - return res.status(500).json({ error: error.message, detail: 'Failed to upsert products' }); + } catch (err) { + console.error('Handler error:', err); + res.status(500).json({ error: err.message }); } - - return res.json({ - success: true, - syncedCount: productsToUpsert.length - }); } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 13aacfb..ab2d2ee 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -54,6 +54,7 @@ export default function App() { const isDev = import.meta.env.DEV; let rows: any[][]; + let allData: any[][]; let fileMeta = { rev: '', size: 0 }; if (isDev) { @@ -67,8 +68,8 @@ export default function App() { const wb = XLSX.read(arrayBuffer, { type: 'array' }); const wsname = wb.SheetNames[0]; const ws = wb.Sheets[wsname]; - const data = XLSX.utils.sheet_to_json(ws, { header: 1 }); - rows = data.slice(1); + allData = XLSX.utils.sheet_to_json(ws, { header: 1 }); + rows = allData.slice(1); fileMeta = { rev: 'dev', size: arrayBuffer.byteLength }; } else { console.log('Fetching file info from Dropbox...'); @@ -87,12 +88,12 @@ export default function App() { const wb = XLSX.read(arrayBuffer, { type: 'array' }); const wsname = wb.SheetNames[0]; const ws = wb.Sheets[wsname]; - const data = XLSX.utils.sheet_to_json(ws, { header: 1 }); - rows = data.slice(1); + allData = XLSX.utils.sheet_to_json(ws, { header: 1 }); + rows = allData.slice(1); } if (rows.length > 0) { - const rawHeaders = (data: any[]) => data[0]; + const headers = allData.slice(0, 1)[0]; console.log('Syncing Excel data to Supabase...'); const syncRes = await fetch('/api/dropbox-sync', { @@ -105,7 +106,8 @@ export default function App() { const syncResult = await syncRes.json(); console.log('Supabase sync result:', syncResult); } else { - console.warn('Supabase sync failed, falling back to direct Excel data'); + const errorText = await syncRes.text(); + console.warn('Supabase sync failed:', syncRes.status, errorText); } console.log('Fetching synced data from Supabase...'); @@ -123,7 +125,7 @@ export default function App() { return finalRow.map((val: any, idx: number) => { if (val === undefined || val === null || val === '') return val; - const header = (rawHeaders[idx] || '').toLowerCase(); + const header = (headers[idx] || '').toLowerCase(); if ((header.includes('id') || header.includes('no') || header.includes('code') || header.includes('art.') || header.includes('barcode') || header.includes('article')) && @@ -149,12 +151,12 @@ export default function App() { }); }); - const asinIdx = (rawHeaders(rows) as string[]).findIndex((h: string) => + const asinIdx = (headers as string[]).findIndex((h: string) => String(h).toLowerCase().trim() === 'asin' ); setAppState({ - headers: rawHeaders(rows), + headers: headers, data: processedRows, fileName: 'Data-Matrix.xlsx (Cloud Sync)', fileDate: new Date(), @@ -194,13 +196,13 @@ export default function App() { const data = XLSX.utils.sheet_to_json(ws, { header: 1 }); if (data.length > 0) { - const rawHeaders = data[0]; + const headers = data[0]; const rawRows = data.slice(1); const processedRows = rawRows.map(row => { return row.map((val, idx) => { if (val === undefined || val === null || val === '') return val; - const header = (rawHeaders[idx] || '').toLowerCase(); + const header = (headers[idx] || '').toLowerCase(); if (header.includes('id') || header.includes('no') || header.includes('code') || header.includes('art.') || header.includes('barcode') || header.includes('article')) { @@ -239,7 +241,7 @@ export default function App() { }); setAppState({ - headers: rawHeaders, + headers: headers, data: processedRows, fileName: file.name, fileDate: new Date(),