Implement parsing for BRI QRIS transactions, including direct and forwarded message handling. Update rules to recognize QRIS success messages and adjust spending record mapping accordingly. Enhance tests to cover new functionality.
Gmail Reader
Monorepo with a React + Vite + DaisyUI client, a Fastify API, and a Gmail push watcher. The API owns Google OAuth, stores tokens in SQLite, and calls the Gmail API with gmail.readonly. The browser only receives an HTTP-only session cookie. The watcher reuses those tokens, calls users.watch, and runs each new INBOX message through a handler pipeline.
Prerequisites
- Node.js 20+
- A Google Cloud OAuth Web application client with:
- Scope:
https://www.googleapis.com/auth/gmail.readonly - Authorized JavaScript origin:
https://oauth.0dev.web.id(andhttp://localhost:5173for local) - Authorized redirect URIs:
https://oauth.0dev.web.id/callbackhttp://localhost:5173/callback(local; Vite proxies this to Fastify)
- Scope:
Local development
cp .env.example .env
Fill in GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and a long SESSION_SECRET. Keep:
GOOGLE_REDIRECT_URI=http://localhost:5173/callback
Then:
npm install
npm run dev
- UI: http://localhost:5173
- API: http://localhost:3000
- Watcher (optional):
npm run dev:watcher→ http://localhost:3001
Vite proxies /auth, /callback, and /api to Fastify so the Google redirect stays on the same origin as the SPA.
Sign in via the UI at least once so SQLite has a refresh token before starting the watcher.
Production (one process)
Build the frontend, then start Fastify. Fastify serves apps/web/dist and handles /callback itself.
npm install
npm run build
NODE_ENV=production GOOGLE_REDIRECT_URI=https://oauth.0dev.web.id/callback npm start
Point your reverse proxy at the Fastify port (PORT, default 3000). Send the whole host to that one process — SPA, /api, /auth, and /callback must share https://oauth.0dev.web.id.
Example Caddy:
oauth.0dev.web.id {
reverse_proxy 127.0.0.1:3000
}
SQLite is stored at apps/api/data/app.db. Keep that directory writable and backed up.
Gmail push watcher
Gmail does not POST to you directly. It publishes to Cloud Pub/Sub, which push-delivers to POST /pubsub/gmail.
GCP setup
- Enable Gmail API and Cloud Pub/Sub in the same Google Cloud project as the OAuth client.
- Create a topic, e.g.
projects/YOUR_PROJECT/topics/gmail-push. - Grant Pub/Sub Publisher on that topic to
serviceAccount:gmail-api-push@system.gserviceaccount.com. - Create a push subscription whose endpoint is publicly reachable HTTPS, e.g.
https://oauth.0dev.web.id/pubsub/gmail?token=YOUR_TOKEN. - Put the topic name and token in
.env(GOOGLE_PUBSUB_TOPIC,PUBSUB_VERIFICATION_TOKEN).
Start after a user has signed in through the web app:
npm run start:watcher
Example Caddy (API on 3000, watcher on 3001):
oauth.0dev.web.id {
handle /pubsub/gmail* {
reverse_proxy 127.0.0.1:3001
}
handle {
reverse_proxy 127.0.0.1:3000
}
}
The watcher renews users.watch on startup and every 12 hours. Handlers run in sequence per new message: log the event, then fetch and log the message body. A handler may pass fields to the next one or break the chain.
What it does
- Sign in with Google (server-side authorization code flow)
- List latest mail
- Search with Gmail query syntax (
from:,subject:,newer_than:, …) - Open a message and read decoded plain-text content
- Watch INBOX via Gmail push (Pub/Sub) and run a sequential handler pipeline (log event, then fetch and log the message)
- Watch INBOX via Gmail push (Pub/Sub) and run a sequential handler pipeline (log event, then fetch and log the message)