refactor(types): type-check batch 1 — 8 tail routes (spending/auth/dataSources/status/profile/import/notifications/categories)
Dropped @ts-nocheck and annotated the fallout (~54 errors, all mechanical: implicit-any helper/callback params + dynamic response-assembly objects in status.cts typed `any` per the shared db.d.ts philosophy). No behavior change — pure annotation. Zero 'possibly undefined' cases in this batch. @ts-nocheck server count: 29 -> 21. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
25404ec4ed
commit
345a159288
|
|
@ -1,9 +1,8 @@
|
||||||
// @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();
|
||||||
|
|
||||||
let _appVersion;
|
let _appVersion: string | undefined;
|
||||||
function getAppVersion() {
|
function getAppVersion() {
|
||||||
if (!_appVersion) {
|
if (!_appVersion) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -196,7 +195,7 @@ router.get('/login-history', requireAuth, (req: Req, res: Res) => {
|
||||||
)
|
)
|
||||||
.all(req.user.id);
|
.all(req.user.id);
|
||||||
|
|
||||||
const safeDecrypt = (v) => {
|
const safeDecrypt = (v: any) => {
|
||||||
if (!v) return null;
|
if (!v) return null;
|
||||||
try {
|
try {
|
||||||
return decryptSecret(v);
|
return decryptSecret(v);
|
||||||
|
|
@ -214,7 +213,7 @@ router.get('/login-history', requireAuth, (req: Req, res: Res) => {
|
||||||
? require('crypto').createHash('sha256').update(currentCookie).digest('hex').slice(0, 32)
|
? require('crypto').createHash('sha256').update(currentCookie).digest('hex').slice(0, 32)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const history = rows.map((r) => ({
|
const history = rows.map((r: any) => ({
|
||||||
id: r.id,
|
id: r.id,
|
||||||
logged_in_at: r.logged_in_at,
|
logged_in_at: r.logged_in_at,
|
||||||
ip_address: safeDecrypt(r.ip_address),
|
ip_address: safeDecrypt(r.ip_address),
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
@ -10,7 +9,7 @@ const { sql } = require('kysely');
|
||||||
|
|
||||||
// Maps a SQLite UNIQUE-constraint violation to a friendly 409; re-throws anything
|
// Maps a SQLite UNIQUE-constraint violation to a friendly 409; re-throws anything
|
||||||
// else so the terminal handler returns a safe 500.
|
// else so the terminal handler returns a safe 500.
|
||||||
function asConflict(e, message, field) {
|
function asConflict(e: any, message: string, field?: string) {
|
||||||
if (e.message?.includes('UNIQUE')) throw ConflictError(message, field);
|
if (e.message?.includes('UNIQUE')) throw ConflictError(message, field);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
@ -57,8 +56,8 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
ORDER BY b.active DESC, b.due_day ASC, b.name COLLATE NOCASE ASC
|
ORDER BY b.active DESC, b.due_day ASC, b.name COLLATE NOCASE ASC
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const shaped = categories.map((category) => {
|
const shaped = categories.map((category: any) => {
|
||||||
const bills = billsByCategory.all(req.user.id, category.id).map((bill) => ({
|
const bills = billsByCategory.all(req.user.id, category.id).map((bill: any) => ({
|
||||||
...bill,
|
...bill,
|
||||||
active: !!bill.active,
|
active: !!bill.active,
|
||||||
payment_count: Number(bill.payment_count || 0),
|
payment_count: Number(bill.payment_count || 0),
|
||||||
|
|
@ -66,9 +65,9 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
last_paid_date: bill.last_paid_date || null,
|
last_paid_date: bill.last_paid_date || null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const activeBillCount = bills.filter((bill) => bill.active).length;
|
const activeBillCount = bills.filter((bill: any) => bill.active).length;
|
||||||
const inactiveBillCount = bills.length - activeBillCount;
|
const inactiveBillCount = bills.length - activeBillCount;
|
||||||
const paymentCount = bills.reduce((sum, bill) => sum + bill.payment_count, 0);
|
const paymentCount = bills.reduce((sum: number, bill: any) => sum + bill.payment_count, 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...category,
|
...category,
|
||||||
|
|
@ -77,7 +76,7 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
active_bill_count: activeBillCount,
|
active_bill_count: activeBillCount,
|
||||||
inactive_bill_count: inactiveBillCount,
|
inactive_bill_count: inactiveBillCount,
|
||||||
payment_count: paymentCount,
|
payment_count: paymentCount,
|
||||||
bill_names: bills.map((bill) => bill.name),
|
bill_names: bills.map((bill: any) => bill.name),
|
||||||
bills,
|
bills,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
@ -129,7 +128,7 @@ router.put('/reorder', (req: Req, res: Res) => {
|
||||||
const update = db.prepare(
|
const update = db.prepare(
|
||||||
"UPDATE categories SET sort_order = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?",
|
"UPDATE categories SET sort_order = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?",
|
||||||
);
|
);
|
||||||
db.transaction((items) => {
|
db.transaction((items: any) => {
|
||||||
for (const item of items) update.run(item.sortOrder, item.categoryId, req.user.id);
|
for (const item of items) update.run(item.sortOrder, item.categoryId, req.user.id);
|
||||||
})(entries);
|
})(entries);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
||||||
|
|
@ -22,14 +21,14 @@ const { syncLimiter } = require('../middleware/rateLimiter.cts');
|
||||||
const VALID_TYPES = new Set(['manual', 'file_import', 'provider_sync']);
|
const VALID_TYPES = new Set(['manual', 'file_import', 'provider_sync']);
|
||||||
const VALID_STATUSES = new Set(['active', 'inactive', 'error']);
|
const VALID_STATUSES = new Set(['active', 'inactive', 'error']);
|
||||||
|
|
||||||
function cleanFilter(value) {
|
function cleanFilter(value: any) {
|
||||||
return typeof value === 'string' ? value.trim() : '';
|
return typeof value === 'string' ? value.trim() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimpleFIN/service errors are deliberately surfaced to the user — but only
|
// SimpleFIN/service errors are deliberately surfaced to the user — but only
|
||||||
// after sanitizeErrorMessage strips tokens/URLs. Wrapping in ApiError keeps
|
// after sanitizeErrorMessage strips tokens/URLs. Wrapping in ApiError keeps
|
||||||
// the sanitized message through the terminal handler (raw 5xx would be masked).
|
// the sanitized message through the terminal handler (raw 5xx would be masked).
|
||||||
function toSourceError(err, fallback, code = 'SIMPLEFIN_ERROR') {
|
function toSourceError(err: any, fallback: string, code = 'SIMPLEFIN_ERROR') {
|
||||||
const msg = sanitizeErrorMessage(err?.message || fallback);
|
const msg = sanitizeErrorMessage(err?.message || fallback);
|
||||||
const status = typeof err?.status === 'number' ? err.status : 500;
|
const status = typeof err?.status === 'number' ? err.status : 500;
|
||||||
return new ApiError(err?.code || code, msg, status);
|
return new ApiError(err?.code || code, msg, status);
|
||||||
|
|
@ -63,7 +62,7 @@ router.get('/accounts/all', (req: Req, res: Res) => {
|
||||||
.all(req.user.id);
|
.all(req.user.id);
|
||||||
|
|
||||||
res.json(
|
res.json(
|
||||||
accounts.map((a) => ({
|
accounts.map((a: any) => ({
|
||||||
...a,
|
...a,
|
||||||
monitored: a.monitored === 1,
|
monitored: a.monitored === 1,
|
||||||
balance_dollars: a.balance !== null ? a.balance / 100 : null,
|
balance_dollars: a.balance !== null ? a.balance / 100 : null,
|
||||||
|
|
@ -211,7 +210,7 @@ router.get('/:sourceId/accounts', (req: Req, res: Res) => {
|
||||||
LIMIT 50
|
LIMIT 50
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const result = accounts.map((acc) => ({
|
const result = accounts.map((acc: any) => ({
|
||||||
...acc,
|
...acc,
|
||||||
monitored: acc.monitored === 1,
|
monitored: acc.monitored === 1,
|
||||||
transactions: acc.monitored === 1 ? txStmt.all(acc.id, req.user.id) : [],
|
transactions: acc.monitored === 1 ? txStmt.all(acc.id, req.user.id) : [],
|
||||||
|
|
@ -310,7 +309,7 @@ router.post('/sync-all', syncLimiter, async (req: Req, res: Res) => {
|
||||||
autoMatched += result.autoMatched ?? 0;
|
autoMatched += result.autoMatched ?? 0;
|
||||||
for (const name of result.matched_bills ?? []) matchedBillSet.add(name);
|
for (const name of result.matched_bills ?? []) matchedBillSet.add(name);
|
||||||
for (const attr of result.late_attributions ?? []) lateAttrAll.push(attr);
|
for (const attr of result.late_attributions ?? []) lateAttrAll.push(attr);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
errors.push(sanitizeErrorMessage(err?.message || 'Sync failed'));
|
errors.push(sanitizeErrorMessage(err?.message || 'Sync 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';
|
||||||
('use strict');
|
('use strict');
|
||||||
|
|
||||||
|
|
@ -25,7 +24,7 @@ function dataImportEnabled() {
|
||||||
return String(process.env.DATA_IMPORT_ENABLED ?? 'true').toLowerCase() !== 'false';
|
return String(process.env.DATA_IMPORT_ENABLED ?? 'true').toLowerCase() !== 'false';
|
||||||
}
|
}
|
||||||
|
|
||||||
function requireDataImportEnabled(req, res, next) {
|
function requireDataImportEnabled(req: Req, res: Res, next: Next) {
|
||||||
if (!dataImportEnabled()) {
|
if (!dataImportEnabled()) {
|
||||||
return res
|
return res
|
||||||
.status(403)
|
.status(403)
|
||||||
|
|
@ -38,7 +37,7 @@ function makeErrorId() {
|
||||||
return `imp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
return `imp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendImportError(res, err, fallback, defaultCode) {
|
function sendImportError(res: Res, err: any, fallback: string, defaultCode: string) {
|
||||||
// Only 4xx service errors carry a user-facing message; a thrown error with
|
// Only 4xx service errors carry a user-facing message; a thrown error with
|
||||||
// an explicit 5xx status must fall through to the masked error-id branch.
|
// an explicit 5xx status must fall through to the masked error-id branch.
|
||||||
if (err.status && err.status < 500) {
|
if (err.status && err.status < 500) {
|
||||||
|
|
@ -423,7 +422,7 @@ router.get('/history', requireDataImportEnabled, (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
const history = getImportHistory(req.user.id);
|
const history = getImportHistory(req.user.id);
|
||||||
res.json({ history });
|
res.json({ history });
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
log.error('[import] history error:', err.message);
|
log.error('[import] history error:', err.message);
|
||||||
res.status(500).json({ error: 'Failed to load import history' });
|
res.status(500).json({ error: 'Failed to load import history' });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
@ -29,7 +28,7 @@ router.get('/admin', requireAuth, requireAdmin, (req: Req, res: Res) => {
|
||||||
'notify_global_recipient',
|
'notify_global_recipient',
|
||||||
'reminder_hour',
|
'reminder_hour',
|
||||||
];
|
];
|
||||||
const settings = {};
|
const settings: Record<string, string> = {};
|
||||||
for (const k of keys) settings[k] = getSetting(k) || '';
|
for (const k of keys) settings[k] = getSetting(k) || '';
|
||||||
if (!settings.reminder_hour) settings.reminder_hour = '6';
|
if (!settings.reminder_hour) settings.reminder_hour = '6';
|
||||||
// Mask password in response
|
// Mask password in response
|
||||||
|
|
@ -65,7 +64,7 @@ router.put('/admin', requireAuth, requireAdmin, (req: Req, res: Res) => {
|
||||||
setSetting('reminder_hour', String(hour));
|
setSetting('reminder_hour', String(hour));
|
||||||
try {
|
try {
|
||||||
require('../workers/dailyWorker.cts').rescheduleDailyWorker();
|
require('../workers/dailyWorker.cts').rescheduleDailyWorker();
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
log.error('[notifications] Failed to reschedule daily worker:', err.message);
|
log.error('[notifications] Failed to reschedule daily worker:', err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +77,7 @@ router.post('/test', requireAuth, requireAdmin, async (req: Req, res: Res) => {
|
||||||
if (!to) throw ValidationError('Recipient address required', 'to');
|
if (!to) throw ValidationError('Recipient address required', 'to');
|
||||||
try {
|
try {
|
||||||
await sendTestEmail(to);
|
await sendTestEmail(to);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// Deliberate pass-through: the SMTP error text is the admin's own config
|
// Deliberate pass-through: the SMTP error text is the admin's own config
|
||||||
// feedback (bad host/auth/TLS). ApiError messages survive formatError;
|
// feedback (bad host/auth/TLS). ApiError messages survive formatError;
|
||||||
// anything not wrapped would be masked as a generic 5xx.
|
// anything not wrapped would be masked as a generic 5xx.
|
||||||
|
|
@ -172,7 +171,7 @@ router.post('/test-push', requireAuth, requireUser, async (req: Req, res: Res) =
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt for sending
|
// Decrypt for sending
|
||||||
const safeDecrypt = (v) => {
|
const safeDecrypt = (v: any) => {
|
||||||
try {
|
try {
|
||||||
return v ? decryptSecret(v) : '';
|
return v ? decryptSecret(v) : '';
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -189,7 +188,7 @@ router.post('/test-push', requireAuth, requireUser, async (req: Req, res: Res) =
|
||||||
try {
|
try {
|
||||||
if (!sendTestPush) throw new Error('Push service not initialised');
|
if (!sendTestPush) throw new Error('Push service not initialised');
|
||||||
await sendTestPush(userForPush);
|
await sendTestPush(userForPush);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// Deliberate pass-through: the push-provider error is the user's own
|
// Deliberate pass-through: the push-provider error is the user's own
|
||||||
// channel-config feedback (bad URL/token) — same rationale as /test above.
|
// channel-config feedback (bad URL/token) — same rationale as /test above.
|
||||||
throw new ApiError('NOTIFICATION_TEST_FAILED', err.message || 'Test push failed', 502);
|
throw new ApiError('NOTIFICATION_TEST_FAILED', err.message || 'Test push failed', 502);
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
||||||
|
|
@ -35,14 +34,14 @@ function dataImportEnabled() {
|
||||||
return String(process.env.DATA_IMPORT_ENABLED ?? 'true').toLowerCase() !== 'false';
|
return String(process.env.DATA_IMPORT_ENABLED ?? 'true').toLowerCase() !== 'false';
|
||||||
}
|
}
|
||||||
|
|
||||||
function requireDataImportEnabled(req, res, next) {
|
function requireDataImportEnabled(req: Req, res: Res, next: Next) {
|
||||||
if (!dataImportEnabled()) {
|
if (!dataImportEnabled()) {
|
||||||
throw ForbiddenError('Data import is disabled by DATA_IMPORT_ENABLED=false');
|
throw ForbiddenError('Data import is disabled by DATA_IMPORT_ENABLED=false');
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
function profileResponse(user) {
|
function profileResponse(user: any) {
|
||||||
const displayName = user.display_name || null;
|
const displayName = user.display_name || null;
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
|
|
@ -287,10 +286,11 @@ router.patch('/settings', (req: Req, res: Res) => {
|
||||||
: null
|
: null
|
||||||
: current.notification_email;
|
: current.notification_email;
|
||||||
|
|
||||||
const boolVal = (incoming, fallback) => (incoming !== undefined ? (incoming ? 1 : 0) : fallback);
|
const boolVal = (incoming: any, fallback: any) =>
|
||||||
|
incoming !== undefined ? (incoming ? 1 : 0) : fallback;
|
||||||
|
|
||||||
// Encrypt push_url and push_token before storing
|
// Encrypt push_url and push_token before storing
|
||||||
const encryptOrNull = (v, fallback) => {
|
const encryptOrNull = (v: any, fallback: any) => {
|
||||||
if (v === undefined) return fallback;
|
if (v === undefined) return fallback;
|
||||||
if (!v) return null;
|
if (!v) return null;
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
||||||
|
|
@ -21,7 +20,7 @@ const {
|
||||||
// Throws ValidationError on bad input; service errors propagate to the
|
// Throws ValidationError on bad input; service errors propagate to the
|
||||||
// terminal handler (4xx with .status pass their message through, 5xx are
|
// terminal handler (4xx with .status pass their message through, 5xx are
|
||||||
// masked + logged there — no per-route try/catch, per CODE_STANDARDS.md).
|
// masked + logged there — no per-route try/catch, per CODE_STANDARDS.md).
|
||||||
function parseYM(source) {
|
function parseYM(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);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
@ -22,7 +21,7 @@ try {
|
||||||
pkg = { name: 'bill-tracker', version: '1.0.0' };
|
pkg = { name: 'bill-tracker', version: '1.0.0' };
|
||||||
}
|
}
|
||||||
|
|
||||||
function errorMessage(err) {
|
function errorMessage(err: any) {
|
||||||
return err?.message || String(err);
|
return err?.message || String(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,7 +29,7 @@ function errorMessage(err) {
|
||||||
// Without the Z suffix, JS Date parses it as LOCAL time, creating timezone
|
// Without the Z suffix, JS Date parses it as LOCAL time, creating timezone
|
||||||
// inconsistencies when mixed with ISO strings that do have Z.
|
// inconsistencies when mixed with ISO strings that do have Z.
|
||||||
// This ensures all timestamps sent to clients are unambiguous UTC ISO strings.
|
// This ensures all timestamps sent to clients are unambiguous UTC ISO strings.
|
||||||
function toIso(sqliteOrIso) {
|
function toIso(sqliteOrIso: any) {
|
||||||
if (!sqliteOrIso) return null;
|
if (!sqliteOrIso) return null;
|
||||||
const s = String(sqliteOrIso).trim();
|
const s = String(sqliteOrIso).trim();
|
||||||
// Already ISO with Z or offset — return as-is
|
// Already ISO with Z or offset — return as-is
|
||||||
|
|
@ -40,7 +39,7 @@ function toIso(sqliteOrIso) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function monthRange(now) {
|
function monthRange(now: any) {
|
||||||
const year = now.getFullYear();
|
const year = now.getFullYear();
|
||||||
const month = now.getMonth() + 1;
|
const month = now.getMonth() + 1;
|
||||||
const daysInMonth = new Date(year, month, 0).getDate();
|
const daysInMonth = new Date(year, month, 0).getDate();
|
||||||
|
|
@ -84,7 +83,7 @@ router.get('/', async (req: Req, res: Res) => {
|
||||||
last_sent_at: null,
|
last_sent_at: null,
|
||||||
last_error: runtimeState.notifications.last_error,
|
last_error: runtimeState.notifications.last_error,
|
||||||
};
|
};
|
||||||
let backups = {
|
let backups: any = {
|
||||||
ok: true,
|
ok: true,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
configured: false,
|
configured: false,
|
||||||
|
|
@ -271,7 +270,7 @@ router.get('/', async (req: Req, res: Res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bank sync (SimpleFIN) status
|
// Bank sync (SimpleFIN) status
|
||||||
let bankSync = {
|
let bankSync: any = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
running: false,
|
running: false,
|
||||||
source_count: 0,
|
source_count: 0,
|
||||||
|
|
@ -351,7 +350,7 @@ router.get('/', async (req: Req, res: Res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup status — safe read-only summary from settings, no paths or secrets
|
// Cleanup status — safe read-only summary from settings, no paths or secrets
|
||||||
let cleanup = { ok: true, last_run_at: null, last_result: null };
|
let cleanup: any = { ok: true, last_run_at: null, last_result: null };
|
||||||
try {
|
try {
|
||||||
const raw = getSetting('cleanup_last_result');
|
const raw = getSetting('cleanup_last_result');
|
||||||
cleanup = {
|
cleanup = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue