From 38bbf367359f18740013418611ffb7cb94861794 Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Thu, 14 May 2026 04:18:29 +0200 Subject: [PATCH] fix(backend): lowercase all column names in buildInsert/buildUpdate and explicit queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All PostgreSQL columns are stored lowercase (PG folds unquoted identifiers). The previous code quoted camelCase keys ("corAgenda", "builderId", etc.) which caused column-not-found errors. - buildInsert: k.toLowerCase() for all column names, no more double-quotes - buildUpdate: k.toLowerCase() for SET clause and WHERE column - dentistas/register: "corAgenda" → coragenda - procedimentos PUT: "procedimentoId" → procedimentoid in DELETE - gto_builders INSERT: "pacienteId"/"dentistaId"/"credenciadaId" → lowercase - gto_items queries: "builderId" → builderid, "valorTotal" → valortotal - guias: "dataSolicitacao"/"beneficiarioNome"/etc → lowercase with normalizedSort Co-Authored-By: Claude Sonnet 4.6 --- backend/server.js | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/backend/server.js b/backend/server.js index fad2f12..57e1e12 100644 --- a/backend/server.js +++ b/backend/server.js @@ -39,22 +39,24 @@ async function withTransaction(fn) { } // Helper: build INSERT query from plain object (PG native) +// Keys are lowercased to match PG's case-folded column storage. function buildInsert(table, obj) { const allowed = Object.entries(obj).filter(([, v]) => v !== undefined && v !== null || typeof v === 'boolean' || v === 0); - const keys = allowed.map(([k]) => k); + const keys = allowed.map(([k]) => k.toLowerCase()); const vals = allowed.map(([, v]) => v); - const cols = keys.map(k => `"${k}"`).join(', '); + const cols = keys.join(', '); const placeholders = keys.map((_, i) => `$${i + 1}`).join(', '); return { text: `INSERT INTO ${table} (${cols}) VALUES (${placeholders})`, values: vals }; } // Helper: build UPDATE SET clause from plain object (PG native) +// Keys are lowercased to match PG's case-folded column storage. function buildUpdate(table, obj, whereCol, whereVal) { - const skip = new Set(['id', 'createdAt', 'created_at']); - const entries = Object.entries(obj).filter(([k, v]) => !skip.has(k) && v !== undefined); - const set = entries.map(([k], i) => `"${k}" = $${i + 1}`).join(', '); + const skip = new Set(['id', 'createdat', 'created_at']); + const entries = Object.entries(obj).filter(([k, v]) => !skip.has(k.toLowerCase()) && v !== undefined); + const set = entries.map(([k], i) => `${k.toLowerCase()} = $${i + 1}`).join(', '); const values = [...entries.map(([, v]) => v), whereVal]; - return { text: `UPDATE ${table} SET ${set} WHERE "${whereCol}" = $${entries.length + 1}`, values }; + return { text: `UPDATE ${table} SET ${set} WHERE ${whereCol.toLowerCase()} = $${entries.length + 1}`, values }; } // Helper: convert PostgreSQL error codes to friendly messages @@ -290,7 +292,7 @@ app.post('/api/dentistas/register', async (req, res) => { // 3. Create or update Dentist record for agenda await client.query(` - INSERT INTO dentistas (id, clinica_id, nome, email, telefone, especialidade, "corAgenda") + INSERT INTO dentistas (id, clinica_id, nome, email, telefone, especialidade, coragenda) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (id) DO UPDATE SET nome = EXCLUDED.nome, especialidade = EXCLUDED.especialidade @@ -917,7 +919,7 @@ app.put('/api/procedimentos/:id', async (req, res) => { await withTransaction(async (client) => { const upd = buildUpdate('procedimentos', updateData, 'id', req.params.id); await client.query(upd.text, upd.values); - await client.query('DELETE FROM procedimentos_valores_planos WHERE "procedimentoId" = $1', [req.params.id]); + await client.query('DELETE FROM procedimentos_valores_planos WHERE procedimentoid = $1', [req.params.id]); if (valoresPlanos && valoresPlanos.length > 0) { for (const v of valoresPlanos) { const vIns = buildInsert('procedimentos_valores_planos', { ...v, procedimentoId: req.params.id }); @@ -1008,13 +1010,13 @@ app.get('/api/guias', async (req, res) => { let paramIdx = 1; if (gto) { - where += ` AND ("numeroGuiaPrestador" LIKE $${paramIdx} OR "numeroGuiaOperadora" LIKE $${paramIdx + 1})`; + where += ` AND (numeroguiaprestador LIKE $${paramIdx} OR numeroguiaoperadora LIKE $${paramIdx + 1})`; params.push(`%${gto}%`, `%${gto}%`); paramIdx += 2; } if (search) { - where += ` AND ("beneficiarioNome" LIKE $${paramIdx} OR "beneficiarioIdentificacao" LIKE $${paramIdx + 1})`; + where += ` AND (beneficiarionome LIKE $${paramIdx} OR beneficiarioidentificacao LIKE $${paramIdx + 1})`; params.push(`%${search}%`, `%${search}%`); paramIdx += 2; } @@ -1026,20 +1028,21 @@ app.get('/api/guias', async (req, res) => { } if (dataInicio) { - where += ` AND "dataSolicitacao" >= $${paramIdx}`; + where += ` AND datasolicitacao >= $${paramIdx}`; params.push(dataInicio); paramIdx++; } if (dataFim) { - where += ` AND "dataSolicitacao" <= $${paramIdx}`; + where += ` AND datasolicitacao <= $${paramIdx}`; params.push(dataFim); paramIdx++; } // Validate sortField and sortOrder to prevent SQL injection - const allowedSortFields = ['dataSolicitacao', 'beneficiarioNome', 'status', 'numeroGuiaPrestador']; - const finalSortField = allowedSortFields.includes(sortField) ? `"${sortField}"` : '"dataSolicitacao"'; + const allowedSortFields = ['datasolicitacao', 'beneficiarionome', 'status', 'numeroguiaprestador']; + const normalizedSort = sortField.toLowerCase(); + const finalSortField = allowedSortFields.includes(normalizedSort) ? normalizedSort : 'datasolicitacao'; const finalSortOrder = sortOrder.toUpperCase() === 'ASC' ? 'ASC' : 'DESC'; const query = ` @@ -1071,7 +1074,7 @@ app.post('/api/gto-builder', async (req, res) => { const { pacienteId, dentistaId, credenciadaId } = req.body; const id = Math.random().toString(36).substring(2, 9).toUpperCase(); await pool.query( - "INSERT INTO gto_builders (id, \"pacienteId\", \"dentistaId\", \"credenciadaId\", status, total) VALUES ($1, $2, $3, $4, 'RASCUNHO', 0)", + "INSERT INTO gto_builders (id, pacienteid, dentistaid, credenciadaid, status, total) VALUES ($1, $2, $3, $4, 'RASCUNHO', 0)", [id, pacienteId, dentistaId, credenciadaId] ); res.json({ id }); @@ -1081,7 +1084,7 @@ app.post('/api/gto-builder', async (req, res) => { app.get('/api/gto-builder/:id', async (req, res) => { try { const { rows: builder } = await pool.query('SELECT * FROM gto_builders WHERE id = $1', [req.params.id]); - const { rows: items } = await pool.query('SELECT * FROM gto_items WHERE "builderId" = $1', [req.params.id]); + const { rows: items } = await pool.query('SELECT * FROM gto_items WHERE builderid = $1', [req.params.id]); res.json({ ...builder[0], items }); } catch (err) { res.status(500).json({ error: err.message }); } }); @@ -1095,7 +1098,7 @@ app.post('/api/gto-builder/:id/item', async (req, res) => { // Update total await pool.query( - 'UPDATE gto_builders SET total = (SELECT SUM("valorTotal") FROM gto_items WHERE "builderId" = $1) WHERE id = $2', + 'UPDATE gto_builders SET total = (SELECT SUM(valortotal) FROM gto_items WHERE builderid = $1) WHERE id = $2', [req.params.id, req.params.id] ); @@ -1108,7 +1111,7 @@ app.delete('/api/gto-builder/:id/item/:itemId', async (req, res) => { await pool.query('DELETE FROM gto_items WHERE id = $1', [req.params.itemId]); // Update total await pool.query( - 'UPDATE gto_builders SET total = COALESCE((SELECT SUM("valorTotal") FROM gto_items WHERE "builderId" = $1), 0) WHERE id = $2', + 'UPDATE gto_builders SET total = COALESCE((SELECT SUM(valortotal) FROM gto_items WHERE builderid = $1), 0) WHERE id = $2', [req.params.id, req.params.id] ); res.json({ success: true });