From 6cf18c049ffdaedac9251349bc1fc9a66972fddf Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Thu, 14 May 2026 06:46:30 +0200 Subject: [PATCH] fix(sync): propagate real tab name in pushTableToSheet and pullTableFromSheet Ensures values.update and values.get use the actual tab title returned by ensureSheetTab (case-insensitive match) so writes don't fail when existing Apps Script tabs have different casing than our expected names. Co-Authored-By: Claude Sonnet 4.6 --- backend/server.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/backend/server.js b/backend/server.js index 0757e3d..7fd1ff7 100644 --- a/backend/server.js +++ b/backend/server.js @@ -290,21 +290,32 @@ async function getSheetsClient() { return google.sheets({ version: 'v4', auth }); } +// Returns the actual tab title as it exists in the sheet (case-preserving). async function ensureSheetTab(sheets, tabName) { const meta = await sheets.spreadsheets.get({ spreadsheetId: SHEETS_BACKUP_ID }); - const exists = meta.data.sheets.some(s => s.properties.title === tabName); - if (!exists) { + const existing = meta.data.sheets.find(s => + s.properties.title.toLowerCase() === tabName.toLowerCase() + ); + if (existing) return existing.properties.title; // already exists, return real name + try { await sheets.spreadsheets.batchUpdate({ spreadsheetId: SHEETS_BACKUP_ID, requestBody: { requests: [{ addSheet: { properties: { title: tabName } } }] } }); + } catch (e) { + if (!e.message?.includes('already exists') && !e.message?.includes('Já existe')) throw e; + // Re-fetch to get the real name after race-condition create + const meta2 = await sheets.spreadsheets.get({ spreadsheetId: SHEETS_BACKUP_ID }); + const found = meta2.data.sheets.find(s => s.properties.title.toLowerCase() === tabName.toLowerCase()); + if (found) return found.properties.title; } + return tabName; } async function pushTableToSheet(tableDef, sheets) { const { rows } = await pool.query(tableDef.query); - await ensureSheetTab(sheets, tableDef.tab); - await sheets.spreadsheets.values.clear({ spreadsheetId: SHEETS_BACKUP_ID, range: `${tableDef.tab}!A:ZZ` }); + const realTab = await ensureSheetTab(sheets, tableDef.tab); + await sheets.spreadsheets.values.clear({ spreadsheetId: SHEETS_BACKUP_ID, range: `${realTab}!A:ZZ` }); const sheetRows = [tableDef.headers, ...rows.map(row => tableDef.headers.map(h => { const v = row[h] ?? row[h.toLowerCase()]; @@ -313,7 +324,7 @@ async function pushTableToSheet(tableDef, sheets) { )]; await sheets.spreadsheets.values.update({ spreadsheetId: SHEETS_BACKUP_ID, - range: `${tableDef.tab}!A1`, + range: `${realTab}!A1`, valueInputOption: 'RAW', requestBody: { values: sheetRows } }); @@ -321,8 +332,8 @@ async function pushTableToSheet(tableDef, sheets) { } async function pullTableFromSheet(tableDef, sheets) { - await ensureSheetTab(sheets, tableDef.tab); - const resp = await sheets.spreadsheets.values.get({ spreadsheetId: SHEETS_BACKUP_ID, range: `${tableDef.tab}!A:ZZ` }); + const realTab = await ensureSheetTab(sheets, tableDef.tab); + const resp = await sheets.spreadsheets.values.get({ spreadsheetId: SHEETS_BACKUP_ID, range: `${realTab}!A:ZZ` }); const sheetRows = resp.data.values || []; if (sheetRows.length < 2) return 0; const headers = sheetRows[0];