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
- // Only fetch actual Interac e-Transfer notification emails
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;