From 803e91da287af25e8d25af070d6a129fad7482a8 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 4 Jun 2026 20:52:50 -0500 Subject: [PATCH] fix: migration error handling for legacy DBs, fallback rejection query --- db/database.js | 16 ++++++++++----- services/matchSuggestionService.js | 32 ++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/db/database.js b/db/database.js index 6228983..4dfc926 100644 --- a/db/database.js +++ b/db/database.js @@ -2893,12 +2893,18 @@ function runMigrations() { const { normalizeMerchant } = require('../services/subscriptionService'); // Re-normalize bill_merchant_rules stored under old normalization ("at t" → "att") - const rules = db.prepare('SELECT id, merchant FROM bill_merchant_rules').all(); - const updBill = db.prepare('UPDATE bill_merchant_rules SET merchant=? WHERE id=?'); let billFixed = 0; - for (const r of rules) { - const fixed = normalizeMerchant(r.merchant); - if (fixed !== r.merchant) { updBill.run(fixed, r.id); billFixed++; } + try { + const rules = db.prepare('SELECT id, merchant FROM bill_merchant_rules').all(); + const updBill = db.prepare('UPDATE bill_merchant_rules SET merchant=? WHERE id=?'); + for (const r of rules) { + try { + const fixed = normalizeMerchant(r.merchant); + if (fixed && fixed !== r.merchant) { updBill.run(fixed, r.id); billFixed++; } + } catch { /* skip invalid entries */ } + } + } catch (err) { + console.warn('[v0.90] bill_merchant_rules re-normalize skipped:', err.message); } // Re-normalize spending_category_rules diff --git a/services/matchSuggestionService.js b/services/matchSuggestionService.js index 1e2a926..89203b8 100644 --- a/services/matchSuggestionService.js +++ b/services/matchSuggestionService.js @@ -119,9 +119,11 @@ function addDateScore(score, reasons, transaction, bill) { function wordBoundaryIncludes(a, b) { if (!a || !b) return false; if (a === b) return true; - const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const wb = s => new RegExp(`(^|\\s)${esc(s)}(\\s|$)`); - return wb(b).test(a) || wb(a).test(b); + try { + const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const wb = s => new RegExp(`(^|\\s)${esc(s)}(\\s|$)`); + return wb(b).test(a) || wb(a).test(b); + } catch { return false; } } function addNameScore(score, reasons, transaction, bill) { @@ -224,13 +226,23 @@ function loadBills(db, userId) { } function loadRejections(db, userId) { - const rows = db.prepare(` - SELECT transaction_id, bill_id - FROM match_suggestion_rejections - WHERE user_id = ? - AND created_at > datetime('now', '-90 days') - `).all(userId); - return new Set(rows.map(row => suggestionId(row.transaction_id, row.bill_id))); + try { + const rows = db.prepare(` + SELECT transaction_id, bill_id + FROM match_suggestion_rejections + WHERE user_id = ? + AND created_at > datetime('now', '-90 days') + `).all(userId); + return new Set(rows.map(row => suggestionId(row.transaction_id, row.bill_id))); + } catch { + // Fall back to all rejections if created_at column doesn't exist yet + try { + const rows = db.prepare(` + SELECT transaction_id, bill_id FROM match_suggestion_rejections WHERE user_id = ? + `).all(userId); + return new Set(rows.map(row => suggestionId(row.transaction_id, row.bill_id))); + } catch { return new Set(); } + } } function loadPriorMatchKeys(db, userId) {