Spaces:
Running
Running
| # ICC Interac Manager β Scan Implementation | |
| ## What This Fixes | |
| The "Scanner maintenant" button now actually **connects to Gmail**, **fetches Interac e-Transfer emails**, **parses them with AI** (Groq/Mistral), and **saves parsed transactions** to the database β all with real-time progress via WebSocket. | |
| --- | |
| ## File Map | |
| ``` | |
| icc-scan-impl/ | |
| βββ .env.example β Copy to .env and fill in API keys | |
| β | |
| βββ shared/ β Shared types & constants | |
| β βββ types/ | |
| β β βββ scan.ts β ScanPreset, ScanDateRange, resolveScanDates() | |
| β β βββ transaction.ts β InteracTransaction schema | |
| β β βββ aiProvider.ts β ProviderSlot, SwitcherEvent | |
| β βββ constants/ | |
| β βββ branches.ts β 48-branch emailβcity mapping | |
| β | |
| βββ server/ β Backend (Express + SQLite) | |
| β βββ index.ts β Server entry point | |
| β βββ db/ | |
| β β βββ database.ts β SQLite schema + operations | |
| β βββ middleware/ | |
| β β βββ auth.ts β JWT middleware | |
| β βββ routes/ | |
| β β βββ auth.ts β Google OAuth routes | |
| β β βββ scan.ts β POST /api/scan/start | |
| β β βββ transactions.ts β GET /api/transactions | |
| β βββ services/ | |
| β β βββ gmailService.ts β Gmail API: query builder + email fetcher | |
| β β βββ aiService.ts β AI parsing: Groq + Mistral + auto-switcher | |
| β β βββ scanService.ts β Scan pipeline: Gmail β AI β DB | |
| β βββ websocket/ | |
| β βββ setup.ts β WebSocket server for progress events | |
| β | |
| βββ web/ β Frontend (React) | |
| βββ services/ | |
| β βββ api.ts β HTTP client with auth | |
| β βββ websocket.ts β WebSocket client | |
| βββ hooks/ | |
| β βββ useEmailScan.ts β React hook for scan state + progress | |
| βββ components/scan/ | |
| β βββ ScanControls.tsx β β THE scan button component | |
| βββ pages/ | |
| βββ AuthCallback.tsx β Handles OAuth redirect | |
| ``` | |
| --- | |
| ## Setup Steps | |
| ### 1. Install Dependencies | |
| In your `packages/server/` directory: | |
| ```bash | |
| npm install express cors helmet dotenv googleapis jsonwebtoken better-sqlite3 ws uuid p-limit zod | |
| npm install -D typescript @types/express @types/cors @types/jsonwebtoken @types/better-sqlite3 @types/ws @types/uuid tsx | |
| ``` | |
| In your `packages/web/` directory (most likely already installed): | |
| ```bash | |
| npm install react-router-dom | |
| ``` | |
| ### 2. Configure Google OAuth | |
| 1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials) | |
| 2. Create a project (or use existing) | |
| 3. Enable the **Gmail API** | |
| 4. Create **OAuth 2.0 Client ID** (Web application) | |
| 5. Add authorized redirect URI: `http://localhost:3001/api/auth/google/callback` | |
| 6. Copy the Client ID and Client Secret | |
| ### 3. Get AI API Keys (Free) | |
| Pick at least one: | |
| - **Groq** (recommended primary): [console.groq.com/keys](https://console.groq.com/keys) β 30 RPM free | |
| - **Mistral** (recommended fallback): [console.mistral.ai/api-keys](https://console.mistral.ai/api-keys) β free tier | |
| ### 4. Create .env File | |
| ```bash | |
| cp .env.example .env | |
| # Edit .env with your actual keys | |
| ``` | |
| ### 5. Copy Files Into Your Project | |
| Copy each file from this package into the corresponding location in your monorepo. | |
| The file paths in this package mirror the target paths in your project. | |
| ### 6. Add Vite Proxy (vite.config.ts) | |
| Make sure your Vite dev server proxies API calls to the backend: | |
| ```typescript | |
| // packages/web/vite.config.ts | |
| export default defineConfig({ | |
| server: { | |
| proxy: { | |
| '/api': 'http://localhost:3001', | |
| '/ws': { | |
| target: 'ws://localhost:3001', | |
| ws: true, | |
| }, | |
| }, | |
| }, | |
| }); | |
| ``` | |
| ### 7. Add Route for Auth Callback | |
| In your React Router config: | |
| ```tsx | |
| import AuthCallback from './pages/AuthCallback'; | |
| // Add this route: | |
| <Route path="/auth/callback" element={<AuthCallback />} /> | |
| ``` | |
| ### 8. Wire Up ScanControls in Your Dashboard | |
| Replace your current scan button section with the new `ScanControls` component: | |
| ```tsx | |
| import ScanControls from '@/components/scan/ScanControls'; | |
| function DashboardPage() { | |
| const { refetch } = useTransactions(); // your existing hook | |
| return ( | |
| <div> | |
| <ScanControls | |
| onScanComplete={(result) => { | |
| // Refresh transaction table after scan completes | |
| refetch(); | |
| }} | |
| /> | |
| {/* ... rest of your dashboard ... */} | |
| </div> | |
| ); | |
| } | |
| ``` | |
| ### 9. Start the Backend | |
| ```bash | |
| cd packages/server | |
| npx tsx src/index.ts | |
| ``` | |
| You should see: | |
| ``` | |
| π ICC Interac Manager API running on http://localhost:3001 | |
| π WebSocket available at ws://localhost:3001/ws | |
| π Google OAuth: http://localhost:3001/api/auth/google | |
| ``` | |
| ### 10. Start the Frontend | |
| ```bash | |
| cd packages/web | |
| npm run dev | |
| ``` | |
| --- | |
| ## How It Works (Flow) | |
| ``` | |
| User clicks "Scanner maintenant" | |
| β | |
| βΌ | |
| ββ ScanControls.tsx βββββββββββββββββββββββββββ | |
| β 1. Reads selected preset (today/7days/custom)β | |
| β 2. Calls api.startScan({ preset, dates }) β | |
| β 3. Connects to WebSocket for progress events β | |
| βββββββββββββββββββββββ¬ββββββββββββββββββββββββ | |
| β POST /api/scan/start | |
| βΌ | |
| ββ scan.ts (route) βββββββββββββββββββββββββββ | |
| β 1. Validates preset + dates β | |
| β 2. Calls scanService.executeScan() β | |
| β 3. Returns immediately (async background) β | |
| βββββββββββββββββββββββ¬βββββββββββββββββββββββ | |
| β | |
| βΌ | |
| ββ scanService.ts ββββββββββββββββββββββββββββ | |
| β PHASE 1: DISCOVER β | |
| β β’ Build Gmail query: from:notify@... β | |
| β β’ Fetch all matching message IDs β | |
| β β’ Deduplicate against existing DB records β | |
| β β | |
| β PHASE 2: PIPELINE (parallel) β | |
| β β’ 10x concurrent Gmail fetches β | |
| β β’ 5x concurrent AI parses β | |
| β β’ Auto-switch GroqβMistral on rate limit β | |
| β β’ Batch INSERT 25 rows at a time β | |
| β β’ Emit scan:progress via WebSocket β | |
| β β | |
| β PHASE 3: COMPLETE β | |
| β β’ Flush remaining DB buffer β | |
| β β’ Emit scan:completed β | |
| β β’ Update scan_logs table β | |
| ββββββββββββββββββββββββββββββββββββββββββββββ | |
| ``` | |
| --- | |
| ## WebSocket Events (Frontend receives these) | |
| | Event | Data | When | | |
| |-------|------|------| | |
| | `scan:started` | `{ jobId, totalEmails, newEmails, skipped }` | Scan begins | | |
| | `scan:progress` | `{ processed, total, errored, latest, currentProvider }` | Each email processed | | |
| | `scan:completed` | `{ jobId, summary: { found, parsed, skipped, errors } }` | Scan finished | | |
| | `scan:error` | `{ jobId, error }` | Scan failed | | |
| | `ai:switch` | `{ from, to, reason }` | AI provider auto-switched | | |
| | `transaction:new` | `{ transaction }` | New transaction saved | | |
| --- | |
| ## Troubleshooting | |
| | Issue | Fix | | |
| |-------|-----| | |
| | "User has no Gmail tokens" | User needs to log in via Google OAuth first: visit `/api/auth/google` | | |
| | "No AI providers configured" | Set `GROQ_API_KEY` and/or `MISTRAL_API_KEY` in `.env` | | |
| | "All AI providers rate-limited" | Wait 60s for cooldown, or add more API keys | | |
| | Scan returns 0 emails | Check Gmail query β make sure the connected account actually has emails from `notify@payments.interac.ca` | | |
| | WebSocket not connecting | Check Vite proxy config includes `/ws` β `ws://localhost:3001` | | |
| | "Invalid token" | JWT expired β log in again via Google OAuth | | |