refactor(types): type-check batch 2 — 7 medium routes + 2 real fixes
Dropped @ts-nocheck from admin/payments/monthly-starting-amounts/ subscriptions/authOidc/calendar/export (~113 errors). Mostly mechanical (catch bindings, params, never[] arrays), but the checker earned its keep: - types/http.d.ts: added the 4-arg res.download(path, name, options, cb) overload — the v0.42.0 root-option download fix (admin backup + user-db export) didn't type-check against the old 3-arg-only signature. - routes/calendar.cts: deleted dead clampDay() (zero callers; was also a standing lint warning). @ts-nocheck server count: 21 -> 14. Server suite 252/252. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
345a159288
commit
6c915a62c8
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
|
|
||||||
import type { Req, Res, Next } from '../types/http';
|
import type { Req, Res, Next } from '../types/http';
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { log } = require('../utils/logger.cts');
|
const { log } = require('../utils/logger.cts');
|
||||||
|
|
@ -44,7 +43,7 @@ const { backupOperationLimiter } = require('../middleware/rateLimiter.cts');
|
||||||
// All routes mounted at /api/admin (requireAuth + requireAdmin applied at server level)
|
// All routes mounted at /api/admin (requireAuth + requireAdmin applied at server level)
|
||||||
|
|
||||||
// Returns a validated positive integer from req.params.id, or null.
|
// Returns a validated positive integer from req.params.id, or null.
|
||||||
function parseUserId(params) {
|
function parseUserId(params: any) {
|
||||||
const n = parseInt(params.id, 10);
|
const n = parseInt(params.id, 10);
|
||||||
return Number.isInteger(n) && n > 0 ? n : null;
|
return Number.isInteger(n) && n > 0 ? n : null;
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +51,7 @@ function parseUserId(params) {
|
||||||
// Backup-ops error path. Kept res-based (not throw-pattern) deliberately: the
|
// Backup-ops error path. Kept res-based (not throw-pattern) deliberately: the
|
||||||
// download route can fail mid-stream after headers are sent, where a throw
|
// download route can fail mid-stream after headers are sent, where a throw
|
||||||
// can't produce a response. Masks 500s; 4xx service messages pass through.
|
// can't produce a response. Masks 500s; 4xx service messages pass through.
|
||||||
function sendError(res, err) {
|
function sendError(res: Res, err: any) {
|
||||||
const status = err.status || 500;
|
const status = err.status || 500;
|
||||||
res.status(status).json({ error: status === 500 ? 'Backup operation failed' : err.message });
|
res.status(status).json({ error: status === 500 ? 'Backup operation failed' : err.message });
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +78,7 @@ router.post('/backups', backupOperationLimiter, async (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
const backup = await createBackup();
|
const backup = await createBackup();
|
||||||
res.status(201).json(backup);
|
res.status(201).json(backup);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -88,7 +87,7 @@ router.post('/backups', backupOperationLimiter, async (req: Req, res: Res) => {
|
||||||
router.get('/backups', backupOperationLimiter, (req: Req, res: Res) => {
|
router.get('/backups', backupOperationLimiter, (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
res.json({ backups: listBackups() });
|
res.json({ backups: listBackups() });
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -97,7 +96,7 @@ router.get('/backups', backupOperationLimiter, (req: Req, res: Res) => {
|
||||||
router.get('/backups/settings', backupOperationLimiter, (req: Req, res: Res) => {
|
router.get('/backups/settings', backupOperationLimiter, (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
res.json(getScheduleStatus());
|
res.json(getScheduleStatus());
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -106,7 +105,7 @@ router.get('/backups/settings', backupOperationLimiter, (req: Req, res: Res) =>
|
||||||
router.put('/backups/settings', backupOperationLimiter, (req: Req, res: Res) => {
|
router.put('/backups/settings', backupOperationLimiter, (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
res.json(saveBackupScheduleSettings(req.body));
|
res.json(saveBackupScheduleSettings(req.body));
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -115,7 +114,7 @@ router.put('/backups/settings', backupOperationLimiter, (req: Req, res: Res) =>
|
||||||
router.post('/backups/run-scheduled-now', backupOperationLimiter, async (req: Req, res: Res) => {
|
router.post('/backups/run-scheduled-now', backupOperationLimiter, async (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
res.status(201).json(await runScheduledBackupNow());
|
res.status(201).json(await runScheduledBackupNow());
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -137,7 +136,7 @@ router.post(
|
||||||
expectedChecksum: expectedChecksum ? String(expectedChecksum).trim() : undefined,
|
expectedChecksum: expectedChecksum ? String(expectedChecksum).trim() : undefined,
|
||||||
});
|
});
|
||||||
res.status(201).json(backup);
|
res.status(201).json(backup);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -159,7 +158,7 @@ router.get('/backups/:id/download', (req: Req, res: Res) => {
|
||||||
if (err && !res.headersSent) sendError(res, err);
|
if (err && !res.headersSent) sendError(res, err);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -168,7 +167,7 @@ router.get('/backups/:id/download', (req: Req, res: Res) => {
|
||||||
router.post('/backups/:id/restore', backupOperationLimiter, async (req: Req, res: Res) => {
|
router.post('/backups/:id/restore', backupOperationLimiter, async (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
res.json(await restoreBackup(req.params.id));
|
res.json(await restoreBackup(req.params.id));
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -177,7 +176,7 @@ router.post('/backups/:id/restore', backupOperationLimiter, async (req: Req, res
|
||||||
router.delete('/backups/:id', backupOperationLimiter, (req: Req, res: Res) => {
|
router.delete('/backups/:id', backupOperationLimiter, (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
res.json(deleteBackup(req.params.id));
|
res.json(deleteBackup(req.params.id));
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -428,7 +427,7 @@ router.delete('/users/:id', (req: Req, res: Res) => {
|
||||||
router.get('/cleanup', (req: Req, res: Res) => {
|
router.get('/cleanup', (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
res.json(getCleanupStatus());
|
res.json(getCleanupStatus());
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -444,7 +443,7 @@ router.get('/cleanup', (req: Req, res: Res) => {
|
||||||
router.put('/cleanup', (req: Req, res: Res) => {
|
router.put('/cleanup', (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
res.json(applyCleanupSettings(req.body));
|
res.json(applyCleanupSettings(req.body));
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -455,7 +454,7 @@ router.post('/cleanup/run', backupOperationLimiter, async (req: Req, res: Res) =
|
||||||
try {
|
try {
|
||||||
const result = await runAllCleanup();
|
const result = await runAllCleanup();
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
sendError(res, err);
|
sendError(res, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -489,7 +488,7 @@ router.post('/auth-mode/oidc-test', async (req: Req, res: Res) => {
|
||||||
router.put('/auth-mode', (req: Req, res: Res) => {
|
router.put('/auth-mode', (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
res.json(applyAuthModeSettings(req.body || {}));
|
res.json(applyAuthModeSettings(req.body || {}));
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
res
|
res
|
||||||
.status(err.status || 500)
|
.status(err.status || 500)
|
||||||
.json({ error: err.status ? err.message : 'Failed to update authentication settings' });
|
.json({ error: err.status ? err.message : 'Failed to update authentication settings' });
|
||||||
|
|
@ -538,7 +537,7 @@ router.post('/migrations/rollback', async (req: Req, res: Res) => {
|
||||||
user_agent: req.get('user-agent'),
|
user_agent: req.get('user-agent'),
|
||||||
});
|
});
|
||||||
res.json({ success: true, ...result });
|
res.json({ success: true, ...result });
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
logAudit({
|
logAudit({
|
||||||
user_id: req.user.id,
|
user_id: req.user.id,
|
||||||
action: 'migration.rollback.failure',
|
action: 'migration.rollback.failure',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
|
|
||||||
import type { Req, Res, Next } from '../types/http';
|
import type { Req, Res, Next } from '../types/http';
|
||||||
('use strict');
|
('use strict');
|
||||||
|
|
||||||
|
|
@ -47,11 +46,11 @@ router.get('/login', async (req: Req, res: Res) => {
|
||||||
const state = createLoginState(redirectTo);
|
const state = createLoginState(redirectTo);
|
||||||
const authUrl = await buildAuthorizationUrl(config, state);
|
const authUrl = await buildAuthorizationUrl(config, state);
|
||||||
res.redirect(authUrl);
|
res.redirect(authUrl);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
const msg = err.message || String(err);
|
const msg = err.message || String(err);
|
||||||
log.error('[oidc] Login initiation error:', msg || '(no message)');
|
log.error('[oidc] Login initiation error:', msg || '(no message)');
|
||||||
if (err.errors) {
|
if (err.errors) {
|
||||||
err.errors.forEach((e, i) => log.error(` [${i}]`, e.message || String(e)));
|
err.errors.forEach((e: any, i: number) => log.error(` [${i}]`, e.message || String(e)));
|
||||||
}
|
}
|
||||||
if (err.cause) log.error(' cause:', err.cause.message || String(err.cause));
|
if (err.cause) log.error(' cause:', err.cause.message || String(err.cause));
|
||||||
res.status(502).json({ error: 'Failed to reach the identity provider. Please try again.' });
|
res.status(502).json({ error: 'Failed to reach the identity provider. Please try again.' });
|
||||||
|
|
@ -111,12 +110,12 @@ router.get('/callback', async (req: Req, res: Res) => {
|
||||||
recordLogin(user.id, req.ip, req.get('user-agent'), session.sessionId);
|
recordLogin(user.id, req.ip, req.get('user-agent'), session.sessionId);
|
||||||
res.cookie(COOKIE_NAME, session.sessionId, cookieOpts(req));
|
res.cookie(COOKIE_NAME, session.sessionId, cookieOpts(req));
|
||||||
res.redirect(savedState.redirect_to || '/');
|
res.redirect(savedState.redirect_to || '/');
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// Log message only — never log tokens, codes, or ID token contents
|
// Log message only — never log tokens, codes, or ID token contents
|
||||||
const msg = err.message || String(err);
|
const msg = err.message || String(err);
|
||||||
log.error('[oidc] Callback error:', msg || '(no message)');
|
log.error('[oidc] Callback error:', msg || '(no message)');
|
||||||
if (err.errors) {
|
if (err.errors) {
|
||||||
err.errors.forEach((e, i) => log.error(` [${i}]`, e.message || String(e)));
|
err.errors.forEach((e: any, i: number) => log.error(` [${i}]`, e.message || String(e)));
|
||||||
}
|
}
|
||||||
if (err.cause) log.error(' cause:', err.cause.message || String(err.cause));
|
if (err.cause) log.error(' cause:', err.cause.message || String(err.cause));
|
||||||
const errCode = err.status === 403 ? 'access_denied' : 'authentication_failed';
|
const errCode = err.status === 403 ? 'access_denied' : 'authentication_failed';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
|
|
||||||
import type { Req, Res, Next } from '../types/http';
|
import type { Req, Res, Next } from '../types/http';
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { ValidationError } = require('../utils/apiError.cts');
|
const { ValidationError } = require('../utils/apiError.cts');
|
||||||
|
|
@ -18,21 +17,16 @@ const {
|
||||||
const { localDateString } = require('../utils/dates.mts');
|
const { localDateString } = require('../utils/dates.mts');
|
||||||
const { roundMoney, sumMoney, fromCents } = require('../utils/money.mts');
|
const { roundMoney, sumMoney, fromCents } = require('../utils/money.mts');
|
||||||
|
|
||||||
function clampDay(year, month, day) {
|
function toDateString(year: number, month: number, day: number) {
|
||||||
const daysInMonth = new Date(year, month, 0).getDate();
|
|
||||||
return Math.min(Math.max(parseInt(day || 1, 10), 1), daysInMonth);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toDateString(year, month, day) {
|
|
||||||
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function emptyDay(year, month, day) {
|
function emptyDay(year: number, month: number, day: number) {
|
||||||
return {
|
return {
|
||||||
date: toDateString(year, month, day),
|
date: toDateString(year, month, day),
|
||||||
day,
|
day,
|
||||||
bills_due: [],
|
bills_due: [] as any[],
|
||||||
payments: [],
|
payments: [] as any[],
|
||||||
status_summary: {
|
status_summary: {
|
||||||
due_count: 0,
|
due_count: 0,
|
||||||
paid_count: 0,
|
paid_count: 0,
|
||||||
|
|
@ -44,7 +38,7 @@ function emptyDay(year, month, day) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function tokenPayload(req, tokenRow) {
|
function tokenPayload(req: Req, tokenRow: any) {
|
||||||
if (!tokenRow) {
|
if (!tokenRow) {
|
||||||
return {
|
return {
|
||||||
active: false,
|
active: false,
|
||||||
|
|
@ -189,7 +183,7 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarBills = bills
|
const calendarBills = bills
|
||||||
.map((bill) => {
|
.map((bill: any) => {
|
||||||
const billRange = getCycleRange(year, month, bill);
|
const billRange = getCycleRange(year, month, bill);
|
||||||
if (!billRange) return null;
|
if (!billRange) return null;
|
||||||
|
|
||||||
|
|
@ -237,9 +231,9 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
if (!bill.is_skipped) day.status_summary.total_due += bill.effective_amount || 0;
|
if (!bill.is_skipped) day.status_summary.total_due += bill.effective_amount || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeBills = calendarBills.filter((bill) => !bill.is_skipped);
|
const activeBills = calendarBills.filter((bill: any) => !bill.is_skipped);
|
||||||
const expectedTotal = sumMoney(activeBills, (bill) => bill.effective_amount || 0);
|
const expectedTotal = sumMoney(activeBills, (bill: any) => bill.effective_amount || 0);
|
||||||
const paidTotal = sumMoney(activeBills, (bill) => bill.paid_amount || 0);
|
const paidTotal = sumMoney(activeBills, (bill: any) => bill.paid_amount || 0);
|
||||||
const remainingTotal = Math.max(0, roundMoney(expectedTotal - paidTotal));
|
const remainingTotal = Math.max(0, roundMoney(expectedTotal - paidTotal));
|
||||||
const paidPercent =
|
const paidPercent =
|
||||||
expectedTotal > 0 ? Math.min(100, Math.round((paidTotal / expectedTotal) * 100)) : 0;
|
expectedTotal > 0 ? Math.min(100, Math.round((paidTotal / expectedTotal) * 100)) : 0;
|
||||||
|
|
@ -261,10 +255,11 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
remaining_total: remainingTotal,
|
remaining_total: remainingTotal,
|
||||||
paid_percent: paidPercent,
|
paid_percent: paidPercent,
|
||||||
bill_count: activeBills.length,
|
bill_count: activeBills.length,
|
||||||
paid_count: activeBills.filter((bill) => bill.is_paid).length,
|
paid_count: activeBills.filter((bill: any) => bill.is_paid).length,
|
||||||
skipped_count: calendarBills.filter((bill) => bill.is_skipped).length,
|
skipped_count: calendarBills.filter((bill: any) => bill.is_skipped).length,
|
||||||
missed_count: activeBills.filter((bill) => bill.status === 'late' || bill.status === 'missed')
|
missed_count: activeBills.filter(
|
||||||
.length,
|
(bill: any) => bill.status === 'late' || bill.status === 'missed',
|
||||||
|
).length,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
|
|
||||||
import type { Req, Res, Next } from '../types/http';
|
import type { Req, Res, Next } from '../types/http';
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { ValidationError } = require('../utils/apiError.cts');
|
const { ValidationError } = require('../utils/apiError.cts');
|
||||||
|
|
@ -16,7 +15,7 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
const format = (req.query.format || 'csv').toLowerCase();
|
const format = (req.query.format || 'csv').toLowerCase();
|
||||||
const { from, to } = req.query;
|
const { from, to } = req.query;
|
||||||
const isDate = (s) => typeof s === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(s);
|
const isDate = (s: any) => typeof s === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(s);
|
||||||
|
|
||||||
// Filter by an explicit date range (both bounds required) or by a single year.
|
// Filter by an explicit date range (both bounds required) or by a single year.
|
||||||
let where, whereParams, label;
|
let where, whereParams, label;
|
||||||
|
|
@ -71,14 +70,14 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
|
|
||||||
if (format === 'csv') {
|
if (format === 'csv') {
|
||||||
const header = 'Date,Bill,Category,Expected,Paid,Method,Notes,Actual Amount,Monthly Notes\n';
|
const header = 'Date,Bill,Category,Expected,Paid,Method,Notes,Actual Amount,Monthly Notes\n';
|
||||||
const escCsv = (v) => {
|
const escCsv = (v: any) => {
|
||||||
if (v == null) return '';
|
if (v == null) return '';
|
||||||
const s = String(v);
|
const s = String(v);
|
||||||
return /[,"\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
return /[,"\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
||||||
};
|
};
|
||||||
|
|
||||||
const body = rows
|
const body = rows
|
||||||
.map((r) => {
|
.map((r: any) => {
|
||||||
const paidMonth = parseInt(r.paid_date.slice(5, 7), 10);
|
const paidMonth = parseInt(r.paid_date.slice(5, 7), 10);
|
||||||
const paidYear = parseInt(r.paid_date.slice(0, 4), 10);
|
const paidYear = parseInt(r.paid_date.slice(0, 4), 10);
|
||||||
const mbs = mbsStmt.get(r.bill_id, paidYear, paidMonth);
|
const mbs = mbsStmt.get(r.bill_id, paidYear, paidMonth);
|
||||||
|
|
@ -102,7 +101,7 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: JSON — enrich each row with monthly_bill_state overrides
|
// Fallback: JSON — enrich each row with monthly_bill_state overrides
|
||||||
const enriched = rows.map((r) => {
|
const enriched = rows.map((r: any) => {
|
||||||
const paidMonth = parseInt(r.paid_date.slice(5, 7), 10);
|
const paidMonth = parseInt(r.paid_date.slice(5, 7), 10);
|
||||||
const paidYear = parseInt(r.paid_date.slice(0, 4), 10);
|
const paidYear = parseInt(r.paid_date.slice(0, 4), 10);
|
||||||
const mbs = mbsStmt.get(r.bill_id, paidYear, paidMonth);
|
const mbs = mbsStmt.get(r.bill_id, paidYear, paidMonth);
|
||||||
|
|
@ -117,7 +116,7 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
res.json({ range: label, count: enriched.length, payments: enriched });
|
res.json({ range: label, count: enriched.length, payments: enriched });
|
||||||
});
|
});
|
||||||
|
|
||||||
function getUserExportData(userId) {
|
function getUserExportData(userId: any) {
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
const categories = db
|
const categories = db
|
||||||
.prepare(
|
.prepare(
|
||||||
|
|
@ -136,7 +135,7 @@ function getUserExportData(userId) {
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
.all(userId)
|
.all(userId)
|
||||||
.map((b) => ({ ...b, expected_amount: fromCents(b.expected_amount) }));
|
.map((b: any) => ({ ...b, expected_amount: fromCents(b.expected_amount) }));
|
||||||
const payments = db
|
const payments = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`
|
`
|
||||||
|
|
@ -150,7 +149,7 @@ function getUserExportData(userId) {
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
.all(userId)
|
.all(userId)
|
||||||
.map((p) => ({ ...p, amount: fromCents(p.amount) }));
|
.map((p: any) => ({ ...p, amount: fromCents(p.amount) }));
|
||||||
const monthlyState = db
|
const monthlyState = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`
|
`
|
||||||
|
|
@ -162,7 +161,7 @@ function getUserExportData(userId) {
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
.all(userId)
|
.all(userId)
|
||||||
.map((m) => ({ ...m, actual_amount: fromCents(m.actual_amount) }));
|
.map((m: any) => ({ ...m, actual_amount: fromCents(m.actual_amount) }));
|
||||||
const monthlyStartingAmounts = db
|
const monthlyStartingAmounts = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`
|
`
|
||||||
|
|
@ -173,7 +172,7 @@ function getUserExportData(userId) {
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
.all(userId)
|
.all(userId)
|
||||||
.map((r) => ({
|
.map((r: any) => ({
|
||||||
...r,
|
...r,
|
||||||
first_amount: fromCents(r.first_amount),
|
first_amount: fromCents(r.first_amount),
|
||||||
fifteenth_amount: fromCents(r.fifteenth_amount),
|
fifteenth_amount: fromCents(r.fifteenth_amount),
|
||||||
|
|
@ -190,13 +189,15 @@ function getUserExportData(userId) {
|
||||||
)
|
)
|
||||||
.all(userId);
|
.all(userId);
|
||||||
const notes = [
|
const notes = [
|
||||||
...bills.filter((b) => b.notes).map((b) => ({ type: 'bill', bill_id: b.id, notes: b.notes })),
|
...bills
|
||||||
|
.filter((b: any) => b.notes)
|
||||||
|
.map((b: any) => ({ type: 'bill', bill_id: b.id, notes: b.notes })),
|
||||||
...payments
|
...payments
|
||||||
.filter((p) => p.notes)
|
.filter((p: any) => p.notes)
|
||||||
.map((p) => ({ type: 'payment', payment_id: p.id, bill_id: p.bill_id, notes: p.notes })),
|
.map((p: any) => ({ type: 'payment', payment_id: p.id, bill_id: p.bill_id, notes: p.notes })),
|
||||||
...monthlyState
|
...monthlyState
|
||||||
.filter((m) => m.notes)
|
.filter((m: any) => m.notes)
|
||||||
.map((m) => ({
|
.map((m: any) => ({
|
||||||
type: 'monthly_state',
|
type: 'monthly_state',
|
||||||
monthly_state_id: m.id,
|
monthly_state_id: m.id,
|
||||||
bill_id: m.bill_id,
|
bill_id: m.bill_id,
|
||||||
|
|
@ -274,7 +275,7 @@ router.get('/user-excel', (req: Req, res: Res) => {
|
||||||
|
|
||||||
// Build the SQLite user-DB export to a temp file and return its path. Extracted
|
// Build the SQLite user-DB export to a temp file and return its path. Extracted
|
||||||
// so the export→import round-trip can be regression-tested (exportImportRoundTrip).
|
// so the export→import round-trip can be regression-tested (exportImportRoundTrip).
|
||||||
function buildUserDbExportFile(userId) {
|
function buildUserDbExportFile(userId: any) {
|
||||||
const data = getUserExportData(userId);
|
const data = getUserExportData(userId);
|
||||||
const file = path.join(os.tmpdir(), `bill-tracker-user-${userId}-${Date.now()}.sqlite`);
|
const file = path.join(os.tmpdir(), `bill-tracker-user-${userId}-${Date.now()}.sqlite`);
|
||||||
const out = new Database(file);
|
const out = new Database(file);
|
||||||
|
|
@ -291,14 +292,14 @@ function buildUserDbExportFile(userId) {
|
||||||
`);
|
`);
|
||||||
const meta = out.prepare('INSERT INTO export_metadata (key, value) VALUES (?, ?)');
|
const meta = out.prepare('INSERT INTO export_metadata (key, value) VALUES (?, ?)');
|
||||||
meta.run('metadata_json', JSON.stringify(data.metadata));
|
meta.run('metadata_json', JSON.stringify(data.metadata));
|
||||||
const insertRows = (table, rows) => {
|
const insertRows = (table: any, rows: any) => {
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
const cols = Object.keys(rows[0]);
|
const cols = Object.keys(rows[0]);
|
||||||
const stmt = out.prepare(
|
const stmt = out.prepare(
|
||||||
`INSERT INTO ${table} (${cols.join(',')}) VALUES (${cols.map(() => '?').join(',')})`,
|
`INSERT INTO ${table} (${cols.join(',')}) VALUES (${cols.map(() => '?').join(',')})`,
|
||||||
);
|
);
|
||||||
const tx = out.transaction((items) =>
|
const tx = out.transaction((items: any) =>
|
||||||
items.forEach((row) => stmt.run(cols.map((c) => row[c]))),
|
items.forEach((row: any) => stmt.run(cols.map((c: any) => row[c]))),
|
||||||
);
|
);
|
||||||
tx(rows);
|
tx(rows);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
|
|
||||||
import type { Req, Res, Next } from '../types/http';
|
import type { Req, Res, Next } from '../types/http';
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
@ -8,7 +7,7 @@ const { accountingActiveSql } = require('../services/paymentAccountingService.ct
|
||||||
const { toCents, fromCents } = require('../utils/money.mts');
|
const { toCents, fromCents } = require('../utils/money.mts');
|
||||||
const { ValidationError } = require('../utils/apiError.cts');
|
const { ValidationError } = require('../utils/apiError.cts');
|
||||||
|
|
||||||
function parseYearMonth(source) {
|
function parseYearMonth(source: any) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const year = parseInt(source.year || now.getFullYear(), 10);
|
const year = parseInt(source.year || now.getFullYear(), 10);
|
||||||
const month = parseInt(source.month || now.getMonth() + 1, 10);
|
const month = parseInt(source.month || now.getMonth() + 1, 10);
|
||||||
|
|
@ -23,12 +22,12 @@ function parseYearMonth(source) {
|
||||||
return { year, month };
|
return { year, month };
|
||||||
}
|
}
|
||||||
|
|
||||||
function money(value) {
|
function money(value: any) {
|
||||||
const n = Number(value);
|
const n = Number(value);
|
||||||
return Number.isFinite(n) ? Math.round(n * 100) / 100 : 0;
|
return Number.isFinite(n) ? Math.round(n * 100) / 100 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStartingAmounts(db, userId, year, month) {
|
function getStartingAmounts(db: any, userId: any, year: any, month: any) {
|
||||||
const row = db
|
const row = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`
|
`
|
||||||
|
|
@ -46,7 +45,7 @@ function getStartingAmounts(db, userId, year, month) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculatePaidDeductions(db, userId, year, month) {
|
function calculatePaidDeductions(db: any, userId: any, year: any, month: any) {
|
||||||
const { start, end } = getCycleRange(year, month);
|
const { start, end } = getCycleRange(year, month);
|
||||||
|
|
||||||
// Paid from first bucket: bills with due_day 1-14
|
// Paid from first bucket: bills with due_day 1-14
|
||||||
|
|
@ -119,7 +118,7 @@ function calculatePaidDeductions(db, userId, year, month) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildStartingAmountsResponse(db, userId, year, month) {
|
function buildStartingAmountsResponse(db: any, userId: any, year: any, month: any) {
|
||||||
const amounts = getStartingAmounts(db, userId, year, month);
|
const amounts = getStartingAmounts(db, userId, year, month);
|
||||||
const paid = calculatePaidDeductions(db, userId, year, month);
|
const paid = calculatePaidDeductions(db, userId, year, month);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
|
|
||||||
import type { Req, Res, Next } from '../types/http';
|
import type { Req, Res, Next } from '../types/http';
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { ValidationError, NotFoundError, ConflictError } = require('../utils/apiError.cts');
|
const { ValidationError, NotFoundError, ConflictError } = require('../utils/apiError.cts');
|
||||||
|
|
@ -22,7 +21,7 @@ const { fromCents } = require('../utils/money.mts');
|
||||||
const SQL_NOT_DELETED = 'deleted_at IS NULL';
|
const SQL_NOT_DELETED = 'deleted_at IS NULL';
|
||||||
const TRANSACTION_MATCH_SOURCE = 'transaction_match';
|
const TRANSACTION_MATCH_SOURCE = 'transaction_match';
|
||||||
|
|
||||||
function isTransactionLinkedPayment(payment) {
|
function isTransactionLinkedPayment(payment: any) {
|
||||||
return payment?.payment_source === TRANSACTION_MATCH_SOURCE || payment?.transaction_id != null;
|
return payment?.payment_source === TRANSACTION_MATCH_SOURCE || payment?.transaction_id != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,7 +33,7 @@ function rejectTransactionLinkedPayment() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseYearMonth(body) {
|
function parseYearMonth(body: any) {
|
||||||
const year = parseInt(body.year, 10);
|
const year = parseInt(body.year, 10);
|
||||||
const month = parseInt(body.month, 10);
|
const month = parseInt(body.month, 10);
|
||||||
if (!Number.isInteger(year) || year < 2000 || year > 2100) {
|
if (!Number.isInteger(year) || year < 2000 || year > 2100) {
|
||||||
|
|
@ -46,7 +45,7 @@ function parseYearMonth(body) {
|
||||||
return { year, month };
|
return { year, month };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAutopaySuggestionContext(db, userId, billId, year, month) {
|
function getAutopaySuggestionContext(db: any, userId: any, billId: any, year: any, month: any) {
|
||||||
const bill = db
|
const bill = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`
|
`
|
||||||
|
|
@ -543,9 +542,9 @@ router.post('/bulk', (req: Req, res: Res) => {
|
||||||
AND p.${SQL_NOT_DELETED}`,
|
AND p.${SQL_NOT_DELETED}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const created = [];
|
const created: any[] = [];
|
||||||
const skipped = [];
|
const skipped: any[] = [];
|
||||||
const errors = [];
|
const errors: any[] = [];
|
||||||
|
|
||||||
const runBulk = db.transaction(() => {
|
const runBulk = db.transaction(() => {
|
||||||
for (const item of payments) {
|
for (const item of payments) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
|
|
||||||
import type { Req, Res, Next } from '../types/http';
|
import type { Req, Res, Next } from '../types/http';
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
@ -69,8 +68,8 @@ router.post('/recommendations/match-bill', (req: Req, res: Res) => {
|
||||||
const billId = parseInt(req.body?.bill_id, 10);
|
const billId = parseInt(req.body?.bill_id, 10);
|
||||||
const rawIds = Array.isArray(req.body?.transaction_ids) ? req.body.transaction_ids : [];
|
const rawIds = Array.isArray(req.body?.transaction_ids) ? req.body.transaction_ids : [];
|
||||||
const txIds = rawIds
|
const txIds = rawIds
|
||||||
.map((id) => parseInt(id, 10))
|
.map((id: any) => parseInt(id, 10))
|
||||||
.filter((n) => Number.isInteger(n) && n > 0)
|
.filter((n: any) => Number.isInteger(n) && n > 0)
|
||||||
.slice(0, 50);
|
.slice(0, 50);
|
||||||
|
|
||||||
if (!Number.isInteger(billId) || billId < 1) {
|
if (!Number.isInteger(billId) || billId < 1) {
|
||||||
|
|
@ -178,7 +177,7 @@ router.post('/recommendations/create', (req: Req, res: Res) => {
|
||||||
metadata: { transaction_ids: req.body?.transaction_ids || [] },
|
metadata: { transaction_ids: req.body?.transaction_ids || [] },
|
||||||
});
|
});
|
||||||
res.status(201).json(created);
|
res.status(201).json(created);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// Service validation errors are user-facing at 4xx (parity with the old
|
// Service validation errors are user-facing at 4xx (parity with the old
|
||||||
// behavior: no-status service throws surfaced at 400, never as masked 5xx).
|
// behavior: no-status service throws surfaced at 400, never as masked 5xx).
|
||||||
throw new ApiError(
|
throw new ApiError(
|
||||||
|
|
@ -222,7 +221,7 @@ router.get('/catalog', (req: Req, res: Res) => {
|
||||||
)
|
)
|
||||||
.all(req.user.id);
|
.all(req.user.id);
|
||||||
|
|
||||||
const billByCatalogId = new Map(matchedBills.map((b) => [b.catalog_id, b]));
|
const billByCatalogId = new Map<any, any>(matchedBills.map((b: any) => [b.catalog_id, b]));
|
||||||
|
|
||||||
// User's custom descriptors
|
// User's custom descriptors
|
||||||
let userDescriptors = [];
|
let userDescriptors = [];
|
||||||
|
|
@ -240,7 +239,7 @@ router.get('/catalog', (req: Req, res: Res) => {
|
||||||
userDescsByCatalogId.get(d.catalog_id).push({ id: d.id, descriptor: d.descriptor });
|
userDescsByCatalogId.get(d.catalog_id).push({ id: d.id, descriptor: d.descriptor });
|
||||||
}
|
}
|
||||||
|
|
||||||
const catalog = catalogEntries.map((entry) => {
|
const catalog = catalogEntries.map((entry: any) => {
|
||||||
const bill = billByCatalogId.get(entry.id) ?? null;
|
const bill = billByCatalogId.get(entry.id) ?? null;
|
||||||
return {
|
return {
|
||||||
...entry,
|
...entry,
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,12 @@ export interface Res {
|
||||||
type(t: string): Res;
|
type(t: string): Res;
|
||||||
redirect(url: string): void;
|
redirect(url: string): void;
|
||||||
download(path: string, filename?: string, cb?: (err?: any) => void): void;
|
download(path: string, filename?: string, cb?: (err?: any) => void): void;
|
||||||
|
download(
|
||||||
|
path: string,
|
||||||
|
filename: string,
|
||||||
|
options: Record<string, any>,
|
||||||
|
cb?: (err?: any) => void,
|
||||||
|
): void;
|
||||||
sendFile(path: string, options?: any, cb?: (err?: any) => void): void;
|
sendFile(path: string, options?: any, cb?: (err?: any) => void): void;
|
||||||
attachment(filename?: string): Res;
|
attachment(filename?: string): Res;
|
||||||
locals: any;
|
locals: any;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue