Spaces:
Sleeping
Sleeping
MichaelEdou Claude Opus 4.6 commited on
Commit ·
c53ba98
1
Parent(s): 5ba5ed4
Broaden Gmail query, clear caches on reset, improve scan logging
Browse files- Widen Interac email search to match multiple sender addresses and subject keywords
- Add clearScanCaches() to reset branch cache and active jobs on data reset
- Add structured logging to scan route for better debugging
- Clean up stale database files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
packages/server/src/routes/emails.ts
CHANGED
|
@@ -3,7 +3,9 @@ import { z } from 'zod';
|
|
| 3 |
import { resolveScanDates } from '@icc/shared';
|
| 4 |
import { requireAuth, type AuthRequest } from '../middleware/auth.js';
|
| 5 |
import { executeScan, getJobStatus, getScanHistory } from '../services/scanService.js';
|
|
|
|
| 6 |
|
|
|
|
| 7 |
const router = Router();
|
| 8 |
|
| 9 |
const scanRequestSchema = z.object({
|
|
@@ -17,6 +19,7 @@ const scanRequestSchema = z.object({
|
|
| 17 |
router.post('/start', requireAuth, async (req: AuthRequest, res) => {
|
| 18 |
const parsed = scanRequestSchema.safeParse(req.body);
|
| 19 |
if (!parsed.success) {
|
|
|
|
| 20 |
res.status(400).json({ error: true, message: 'Paramètres de scan invalides', details: parsed.error.issues });
|
| 21 |
return;
|
| 22 |
}
|
|
@@ -25,6 +28,7 @@ router.post('/start', requireAuth, async (req: AuthRequest, res) => {
|
|
| 25 |
|
| 26 |
try {
|
| 27 |
const dateRange = resolveScanDates(preset, startDate, endDate);
|
|
|
|
| 28 |
|
| 29 |
const jobId = await executeScan(
|
| 30 |
{ dateRange, forceRescan },
|
|
@@ -33,6 +37,7 @@ router.post('/start', requireAuth, async (req: AuthRequest, res) => {
|
|
| 33 |
|
| 34 |
res.json({ jobId, dateRange, status: 'queued' });
|
| 35 |
} catch (err: any) {
|
|
|
|
| 36 |
res.status(500).json({ error: true, message: err.message });
|
| 37 |
}
|
| 38 |
});
|
|
|
|
| 3 |
import { resolveScanDates } from '@icc/shared';
|
| 4 |
import { requireAuth, type AuthRequest } from '../middleware/auth.js';
|
| 5 |
import { executeScan, getJobStatus, getScanHistory } from '../services/scanService.js';
|
| 6 |
+
import pino from 'pino';
|
| 7 |
|
| 8 |
+
const logger = pino({ level: 'info', transport: { target: 'pino-pretty', options: { colorize: true } } });
|
| 9 |
const router = Router();
|
| 10 |
|
| 11 |
const scanRequestSchema = z.object({
|
|
|
|
| 19 |
router.post('/start', requireAuth, async (req: AuthRequest, res) => {
|
| 20 |
const parsed = scanRequestSchema.safeParse(req.body);
|
| 21 |
if (!parsed.success) {
|
| 22 |
+
logger.warn({ issues: parsed.error.issues }, 'Invalid scan request');
|
| 23 |
res.status(400).json({ error: true, message: 'Paramètres de scan invalides', details: parsed.error.issues });
|
| 24 |
return;
|
| 25 |
}
|
|
|
|
| 28 |
|
| 29 |
try {
|
| 30 |
const dateRange = resolveScanDates(preset, startDate, endDate);
|
| 31 |
+
logger.info({ preset, dateRange, forceRescan, userId: req.userId }, 'Scan requested: %s (%s → %s)', preset, dateRange.startDate, dateRange.endDate);
|
| 32 |
|
| 33 |
const jobId = await executeScan(
|
| 34 |
{ dateRange, forceRescan },
|
|
|
|
| 37 |
|
| 38 |
res.json({ jobId, dateRange, status: 'queued' });
|
| 39 |
} catch (err: any) {
|
| 40 |
+
logger.error({ err: err.message, preset, userId: req.userId }, 'Scan start failed');
|
| 41 |
res.status(500).json({ error: true, message: err.message });
|
| 42 |
}
|
| 43 |
});
|
packages/server/src/routes/settings.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { eq, sql } from 'drizzle-orm';
|
|
| 6 |
import { requireAuth, type AuthRequest } from '../middleware/auth.js';
|
| 7 |
import { AIService } from '../services/aiService.js';
|
| 8 |
import { config } from '../config/env.js';
|
|
|
|
| 9 |
|
| 10 |
const router = Router();
|
| 11 |
|
|
@@ -143,6 +144,9 @@ router.post('/reset', requireAuth, async (req: AuthRequest, res) => {
|
|
| 143 |
db.delete(branchConfig).run();
|
| 144 |
db.delete(aiSettings).run();
|
| 145 |
|
|
|
|
|
|
|
|
|
|
| 146 |
res.json({ success: true, message: 'All data has been reset' });
|
| 147 |
} catch (err: any) {
|
| 148 |
res.status(500).json({ error: true, message: err.message || 'Reset failed' });
|
|
|
|
| 6 |
import { requireAuth, type AuthRequest } from '../middleware/auth.js';
|
| 7 |
import { AIService } from '../services/aiService.js';
|
| 8 |
import { config } from '../config/env.js';
|
| 9 |
+
import { clearScanCaches } from '../services/scanService.js';
|
| 10 |
|
| 11 |
const router = Router();
|
| 12 |
|
|
|
|
| 144 |
db.delete(branchConfig).run();
|
| 145 |
db.delete(aiSettings).run();
|
| 146 |
|
| 147 |
+
// Clear in-memory caches
|
| 148 |
+
clearScanCaches();
|
| 149 |
+
|
| 150 |
res.json({ success: true, message: 'All data has been reset' });
|
| 151 |
} catch (err: any) {
|
| 152 |
res.status(500).json({ error: true, message: err.message || 'Reset failed' });
|
packages/server/src/services/gmailService.ts
CHANGED
|
@@ -19,6 +19,8 @@ export function buildGmailQuery(dateRange: ScanDateRange): string {
|
|
| 19 |
const start = new Date(dateRange.startDate);
|
| 20 |
const end = new Date(dateRange.endDate);
|
| 21 |
|
|
|
|
|
|
|
| 22 |
const afterDate = new Date(start);
|
| 23 |
afterDate.setDate(afterDate.getDate() - 1);
|
| 24 |
|
|
@@ -27,8 +29,8 @@ export function buildGmailQuery(dateRange: ScanDateRange): string {
|
|
| 27 |
|
| 28 |
const fmt = (d: Date) => `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
| 29 |
|
| 30 |
-
//
|
| 31 |
-
return `from:notify@payments.interac.ca after:${fmt(afterDate)} before:${fmt(beforeDate)}`;
|
| 32 |
}
|
| 33 |
|
| 34 |
function getOAuth2Client() {
|
|
|
|
| 19 |
const start = new Date(dateRange.startDate);
|
| 20 |
const end = new Date(dateRange.endDate);
|
| 21 |
|
| 22 |
+
// Gmail after:/before: are exclusive date boundaries (midnight-based).
|
| 23 |
+
// Subtract 1 day from start and add 1 day to end for inclusivity.
|
| 24 |
const afterDate = new Date(start);
|
| 25 |
afterDate.setDate(afterDate.getDate() - 1);
|
| 26 |
|
|
|
|
| 29 |
|
| 30 |
const fmt = (d: Date) => `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
| 31 |
|
| 32 |
+
// Broader query: match Interac notification sender OR subject keywords
|
| 33 |
+
return `(from:notify@payments.interac.ca OR from:interac.ca OR subject:"virement Interac" OR subject:"Interac e-Transfer") after:${fmt(afterDate)} before:${fmt(beforeDate)}`;
|
| 34 |
}
|
| 35 |
|
| 36 |
function getOAuth2Client() {
|
packages/server/src/services/scanService.ts
CHANGED
|
@@ -68,6 +68,13 @@ export function getJobStatus(jobId: string): ScanJobState | undefined {
|
|
| 68 |
return activeJobs.get(jobId);
|
| 69 |
}
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
// User-scoped scan history (newest first)
|
| 72 |
export async function getScanHistory(userId: string) {
|
| 73 |
return db.select().from(scanLogs).where(eq(scanLogs.userId, userId)).orderBy(desc(scanLogs.id)).limit(100);
|
|
@@ -133,7 +140,7 @@ async function runScan(jobId: string, scanRequest: ScanRequest, userId: string,
|
|
| 133 |
// Phase 1: Email discovery
|
| 134 |
jobState.status = 'scanning';
|
| 135 |
const query = buildGmailQuery(dateRange);
|
| 136 |
-
logger.info({ jobId, query }, 'Starting email discovery');
|
| 137 |
|
| 138 |
const messageIds = await fetchAllMessageIds(userId, query);
|
| 139 |
jobState.progress.emailsFound = messageIds.length;
|
|
|
|
| 68 |
return activeJobs.get(jobId);
|
| 69 |
}
|
| 70 |
|
| 71 |
+
/** Clear all in-memory caches (branch cache + active jobs) */
|
| 72 |
+
export function clearScanCaches() {
|
| 73 |
+
_branchCache = null;
|
| 74 |
+
_branchCacheTime = 0;
|
| 75 |
+
activeJobs.clear();
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
// User-scoped scan history (newest first)
|
| 79 |
export async function getScanHistory(userId: string) {
|
| 80 |
return db.select().from(scanLogs).where(eq(scanLogs.userId, userId)).orderBy(desc(scanLogs.id)).limit(100);
|
|
|
|
| 140 |
// Phase 1: Email discovery
|
| 141 |
jobState.status = 'scanning';
|
| 142 |
const query = buildGmailQuery(dateRange);
|
| 143 |
+
logger.info({ jobId, query, dateRange }, 'Starting email discovery — query: %s', query);
|
| 144 |
|
| 145 |
const messageIds = await fetchAllMessageIds(userId, query);
|
| 146 |
jobState.progress.emailsFound = messageIds.length;
|