nsrb c446d794a8 feat(watcher): add Tokopedia order parsing and rules
Implement parsing for Tokopedia "Pesanan Selesai" messages, including support for single and multi-item orders. Update rules to recognize Tokopedia order completion and enhance spending record mapping. Add tests to validate new functionality.
2026-09-13 23:58:23 +07:00
2026-09-13 22:56:22 +07:00

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 (and http://localhost:5173 for local)
    • Authorized redirect URIs:
      • https://oauth.0dev.web.id/callback
      • http://localhost:5173/callback (local; Vite proxies this to Fastify)

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

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

  1. Enable Gmail API and Cloud Pub/Sub in the same Google Cloud project as the OAuth client.
  2. Create a topic, e.g. projects/YOUR_PROJECT/topics/gmail-push.
  3. Grant Pub/Sub Publisher on that topic to serviceAccount:gmail-api-push@system.gserviceaccount.com.
  4. Create a push subscription whose endpoint is publicly reachable HTTPS, e.g. https://oauth.0dev.web.id/pubsub/gmail?token=YOUR_TOKEN.
  5. 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)
S
Description
No description provided
Readme
264 KiB
Languages
JavaScript 99.7%
HTML 0.2%
CSS 0.1%