From d6da89f48af359048f2905222693a0df4c78ba19 Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Tue, 16 Jun 2026 18:36:35 +0200 Subject: [PATCH] =?UTF-8?q?chore(accounting):=20PR=20#622=20nits=20?= =?UTF-8?q?=E2=80=94=20stray=20artifact,=20dedupe=20requireFlag,=20IMAP=20?= =?UTF-8?q?poll=20backoff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Remove the committed test artifact backend/storage/business-docs/quote/2026/ Q-2026-0001.pdf and gitignore backend/storage/business-docs/ so generated CRM docs can't be committed again. 2. adminLedger + adminExpenses dropped their local requireFlag copies and now import the shared (now cached) requireFeatureFlag middleware. 4. roundTripTest polls IMAP with ×1.5 backoff (cap 8s) instead of a flat 3s, so a 30s test takes ~5 SELECT/SEARCH locks not ~10 (some servers throttle). Nit 3 (dashboard + events pages still on the gallery-theme vars, not dark-mode- swapped) is left as a documented follow-up per the review. --- .gitignore | 3 +++ backend/src/routes/adminExpenses.js | 18 +++++------------- backend/src/routes/adminLedger.js | 14 +++----------- backend/src/services/emailIntakeService.js | 6 +++++- .../business-docs/quote/2026/Q-2026-0001.pdf | 1 - 5 files changed, 16 insertions(+), 26 deletions(-) delete mode 100644 backend/storage/business-docs/quote/2026/Q-2026-0001.pdf diff --git a/.gitignore b/.gitignore index 52f31cf6..956c3b23 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,6 @@ docker-compose.dev.yml # New layout development files new-layouts/ + +# Generated CRM/accounting documents (runtime) — never commit +backend/storage/business-docs/ diff --git a/backend/src/routes/adminExpenses.js b/backend/src/routes/adminExpenses.js index a79923f5..4ef9eb1b 100644 --- a/backend/src/routes/adminExpenses.js +++ b/backend/src/routes/adminExpenses.js @@ -48,19 +48,11 @@ function diskUpload(subdir) { const inboundUpload = diskUpload('inbound'); const proofUpload = diskUpload('expenses/proof'); -function requireFlag(key, code) { - return async (req, res, next) => { - try { - const row = await db('feature_flags').where({ key }).first(); - const enabled = row && (row.value === true || row.value === 1 || row.value === '1'); - if (!enabled) return res.status(403).json({ error: `${key} feature is disabled`, code }); - return next(); - } catch (err) { return next(err); } - }; -} -const requireIncoming = requireFlag('incomingInvoices', 'INCOMING_INVOICES_DISABLED'); -const requireExpenses = requireFlag('expenses', 'EXPENSES_DISABLED'); -const requireAccounting = requireFlag('accounting', 'ACCOUNTING_DISABLED'); +// Shared cached feature gate (PR #622 nit 2) — replaces the former local copy. +const { requireFeatureFlag } = require('../middleware/requireFeatureFlag'); +const requireIncoming = requireFeatureFlag('incomingInvoices', 'INCOMING_INVOICES_DISABLED'); +const requireExpenses = requireFeatureFlag('expenses', 'EXPENSES_DISABLED'); +const requireAccounting = requireFeatureFlag('accounting', 'ACCOUNTING_DISABLED'); router.use(adminAuth); diff --git a/backend/src/routes/adminLedger.js b/backend/src/routes/adminLedger.js index fdd8cc83..3a948edb 100644 --- a/backend/src/routes/adminLedger.js +++ b/backend/src/routes/adminLedger.js @@ -21,17 +21,9 @@ const ledgerService = require('../services/ledgerService'); const router = express.Router(); const toInt = (v) => { const n = parseInt(v, 10); return Number.isFinite(n) ? n : undefined; }; -function requireFlag(key, code) { - return async (req, res, next) => { - try { - const row = await db('feature_flags').where({ key }).first(); - const enabled = row && (row.value === true || row.value === 1 || row.value === '1'); - if (!enabled) return res.status(403).json({ error: `${key} feature is disabled`, code }); - return next(); - } catch (err) { return next(err); } - }; -} -const requireAccounting = requireFlag('accounting', 'ACCOUNTING_DISABLED'); +// Shared cached feature gate (PR #622 nit 2) — replaces the former local copy. +const { requireFeatureFlag } = require('../middleware/requireFeatureFlag'); +const requireAccounting = requireFeatureFlag('accounting', 'ACCOUNTING_DISABLED'); router.use(adminAuth); router.use(requireAccounting); diff --git a/backend/src/services/emailIntakeService.js b/backend/src/services/emailIntakeService.js index 2a3bd3ff..e96bff60 100644 --- a/backend/src/services/emailIntakeService.js +++ b/backend/src/services/emailIntakeService.js @@ -202,6 +202,9 @@ async function roundTripTest({ timeoutMs = 30000, intervalMs = 3000 } = {}) { const client = makeImapClient(cfg); await connectWithTimeout(client); const started = Date.now(); + // Backoff (PR #622 nit 4): some IMAP servers throttle frequent SELECT/SEARCH. + // Grow the gap ×1.5 (cap 8s) so a 30s test does ~5 polls, not ~10. + let delay = intervalMs; try { // eslint-disable-next-line no-constant-condition while (true) { @@ -219,7 +222,8 @@ async function roundTripTest({ timeoutMs = 30000, intervalMs = 3000 } = {}) { return { ok: false, sent: true, reason: 'not_received', recipient }; } // eslint-disable-next-line no-await-in-loop - await new Promise((r) => setTimeout(r, intervalMs)); + await new Promise((r) => setTimeout(r, delay)); + delay = Math.min(Math.round(delay * 1.5), 8000); } } finally { await client.logout().catch(() => {}); diff --git a/backend/storage/business-docs/quote/2026/Q-2026-0001.pdf b/backend/storage/business-docs/quote/2026/Q-2026-0001.pdf deleted file mode 100644 index 6089659d..00000000 --- a/backend/storage/business-docs/quote/2026/Q-2026-0001.pdf +++ /dev/null @@ -1 +0,0 @@ -pdf \ No newline at end of file