From e189cc3045c198eb3980ca362c1fc249aa350e2e Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Thu, 19 Mar 2026 12:06:27 +0100 Subject: [PATCH] fix(sync): ranking dedup + copy logs over HTTP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ranking.js: déduplique par (gencod, site) avant batchUpsert pour éviter "ON CONFLICT DO UPDATE command cannot affect row a second time" - server.js: bouton "Copier les logs" utilise execCommand fallback quand navigator.clipboard indisponible (accès HTTP sans HTTPS) Co-Authored-By: Claude Opus 4.6 --- sync/server.js | 27 ++++++++++++++++----------- sync/tables/ranking.js | 7 ++++++- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/sync/server.js b/sync/server.js index ea471d4..480ab9f 100644 --- a/sync/server.js +++ b/sync/server.js @@ -280,17 +280,22 @@ async function refresh(){ function copyLogs(){ const box = document.getElementById('logBox'); const text = Array.from(box.querySelectorAll('.log-line')).map(l => l.textContent).join('\\n'); - navigator.clipboard.writeText(text).then(() => { - const btn = document.getElementById('btnCopyLogs'); - btn.textContent = 'Copié !'; - setTimeout(() => { btn.textContent = 'Copier les logs'; }, 2000); - }).catch(() => { - const sel = window.getSelection(); - const range = document.createRange(); - range.selectNodeContents(box); - sel.removeAllRanges(); - sel.addRange(range); - }); + const btn = document.getElementById('btnCopyLogs'); + function onSuccess(){ btn.textContent = 'Copié !'; setTimeout(() => { btn.textContent = 'Copier les logs'; }, 2000); } + if(navigator.clipboard && window.isSecureContext){ + navigator.clipboard.writeText(text).then(onSuccess).catch(fallback); + } else { + fallback(); + } + function fallback(){ + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.cssText = 'position:fixed;top:-9999px;left:-9999px;opacity:0'; + document.body.appendChild(ta); + ta.focus(); ta.select(); + try{ document.execCommand('copy'); onSuccess(); } catch(e){ alert('Copie manuelle : sélectionnez les logs et Ctrl+C'); } + document.body.removeChild(ta); + } } async function runSync(){ diff --git a/sync/tables/ranking.js b/sync/tables/ranking.js index acc2080..c65a5dd 100644 --- a/sync/tables/ranking.js +++ b/sync/tables/ranking.js @@ -46,7 +46,12 @@ async function syncRanking(force) { date_calcul_mag: r.DATE_CALCUL_MAG || null, })); - const count = await batchUpsert(pg, 'ranking', rows, ['gencod', 'site'], COLS); + // Dédupliquer par (gencod, site) — garder la dernière occurrence + const seen = new Map(); + for (const r of rows) seen.set(`${r.gencod}|${r.site}`, r); + const deduped = Array.from(seen.values()); + + const count = await batchUpsert(pg, 'ranking', deduped, ['gencod', 'site'], COLS); await logSync(pg, 'ranking', count, 'ok'); console.log(`[ranking] ${count} lignes upsert`); } catch (err) {