chore(accounting): PR #622 nits — stray artifact, dedupe requireFlag, IMAP poll backoff

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.
This commit is contained in:
Luca
2026-06-16 18:36:35 +02:00
parent a93b6dc232
commit d6da89f48a
5 changed files with 16 additions and 26 deletions
+5 -13
View File
@@ -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);
+3 -11
View File
@@ -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);
+5 -1
View File
@@ -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(() => {});
@@ -1 +0,0 @@
pdf