From 8fa9f4350e0d56850cc0f50fa68390f2ad0a04de Mon Sep 17 00:00:00 2001 From: Nasyarobby Putra Date: Sun, 13 Sep 2026 22:56:22 +0700 Subject: [PATCH] first commit --- README.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2a3e98 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# 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 + +```bash +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: + +```bash +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. + +```bash +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: + +```bash +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)